cf11 D. A Simple Task

cf11 D. A Simple Task

题意:

求简单图(无向、无重边、无自环)中简单环(不重复经过点/边)的数量

\(1\le n\le 19\)

思路:

能不能暴力嗯dp?\(dp(i,j)\) 表示起点为 \(i\),终点为 \(j\) 的方案数。这样会把非简单环算进去。

考虑状压,\(f(S,i)\) 表示路径上的所有点组成点集 \(S\),起点是 \(S\) 中编号最小的点(记为 \(start\))(思想类似 lowbit),终点是 \(i\) 的方案数。

\(i\) 扩展到点 \(j\)

  • 如果 \(j\)\(start\) 还小,忽略之

  • 对于 \(j\in S\),如果 \(j\) 就是 \(start\),说明找到了一个环,更新答案;否则就走到了路径上走过的点,不是简单环了,忽略之

  • 对于 \(j\notin S\),更新 \(f(S,i)\to f(S+'j',i)\)

更新的顺序:从小到大枚举所有可能的 \(S\) 就好了。这样循环到 \(S\) 之前,\(S\) 一定被所有能扩展到 \(S\) 的状态扩展过

注意每个简单环被算了两次(顺时针和逆时针);另外每个 \(x\to y\to x\) 的只有两条边的环也是非法的,这种有 \(m\)

所以输出 \((ans-m)/2\)

当然也可以用 __builtin_popcount\(S\) 中的点数,当环中点数大于2时才更新ans,这样最后只需除以2,不用减m

const signed N = 19; //开到21会MLE
int n, m; bool g[N][N];
ll f[1<<N][N], ans;

int get(int S) { //非空集S的起点
    for(int i = 0; ; i++)
        if(S>>i&1) return i;
}
bool in(int S, int x) { //u是否在集合S中
    return (S>>x&1);
}

signed main() {
    iofast;
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        int u, v; cin >> u >> v;
        u--, v--; //二进制,-1比较方便
        g[u][v] = g[v][u] = 1;
    }

    for(int i = 0; i < n; i++) f[1<<i][i] = 1; //初始化

    for(int S = 1; S < (1<<n); S++) {
        int start = get(S);
        for(int i = 0; i < n; i++) {
            if(!f[S][i]) continue; //不存在的状态
            if(g[i][start]) ans += f[S][i];
            for(int j = start+1; j < n; j++)
                if(g[i][j] && !in(S,j))
                    f[S|(1<<j)][j] += f[S][i];
        }
    }

    cout << (ans-m)/2;
}

posted @ 2022-04-09 13:12  Bellala  阅读(28)  评论(0)    收藏  举报