洛谷

洛谷题单指南-状态压缩动态规划-CF11D A Simple Task

原题链接:https://www.luogu.com.cn/problem/CF11D

题意解读:计算无向图中简单环的数量。

解题思路:

要计算环的数量,就需要计算每个点经过一系列点回到自己的路径条数。

为了避免重复计算,设定路径上经过的点中编号最小的是起点,要记录再次回到起点的路径条数!

可以这样来定义状态,设f[i][j]表示经过的点的状态为i,且最新到达的点为j的路径条数。

对于j相邻的所有点k,

如果k比i中最小的点还小,不合法的状态,不转移;

如果k在i中出现过,且就是i中最小的点,说明这条路径形成了环,要把答案累加f[i][j];

如果k在i中出现过,但不是最小的点,忽略掉;i中最小的点就是lowbit(i)的位置;

如果k在i中没有出现过,且是合法的点,则转移f[i | (1 << k)][k] += f[i][j]。

初始化根据定义来,对于所有点i,f[1<<i][i] = 1。

由于双向边的建立,此前环的统计算上了所有的二元环一次,其他环也都算了两次(顺时针、逆时针),最终答案为(ans - m) / 2。
100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 20, M = 400;

int head[N], to[M], nxt[M], idx;
long long f[1 << N][N];
int n, m;
long long ans;

void add(int a, int b)
{
    to[++idx] = b;
    nxt[idx] = head[a];
    head[a] = idx;
}

int lowbit(int x)
{
    return x & -x;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        int u, v;
        cin >> u >> v;
        u--, v--; //为便于状态压缩后二进制位置表示点,编号从0开始更易处理
        add(u, v), add(v, u);
    }

    for(int i = 0; i < n; i++) f[1 << i][i] = 1;
    for(int i = 1; i < 1 << n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(!f[i][j]) continue; //不是一个有效的初始状态
            for(int x = head[j]; x; x = nxt[x])
            {
                int k = to[x];
                if(lowbit(i) > (1 << k)) continue; //k编号比i中最小的还小,不合法
                if(i >> k & 1) //k是i中的点
                {
                    if(lowbit(i) == (1 << k)) ans += f[i][j]; //k是i中的最小点,是环
                }
                else f[i | (1 << k)][k] += f[i][j];
            }
        }
    }

    cout << (ans - m) / 2; //由于双向边的建立,此前环的统计算上了所有的二元环,其他环也都算了两次
    return 0;
}