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;
}