cf11 D. A Simple Task

cf11 D. A Simple Task

题意:

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

1n19

思路:

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

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

i 扩展到点 j

  • 如果 jstart 还小,忽略之

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

  • 对于 jS,更新 f(S,i)f(S+j,i)

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

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

所以输出 (ansm)/2

当然也可以用 __builtin_popcountS 中的点数,当环中点数大于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 @   Bellala  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示