●BZOJ 2560 串珠子
题链:
http://www.lydsy.com/JudgeOnline/problem.php?id=2560
题解:
容斥,状压计数dp
首先求出一个数组 g[s] 表示集合内的点的连边方案数(两个点间包括不连边和连边)
即 n
g[s]= ∏ (c[i][j]+1)
i,j∈s
然后定义一个 f[s] 表示 s的集合构成联通图的方案数。
直接f[s]=g[s]么?显然不是,因为 g[s]里还包含有不联通的情况。
所以需要减去一些东西。 一下的操作就比较妙了。
考虑把 s集合里的最小元素 A 固定,使得集合内的所有点都要在 A 的联通图里。
然后枚举 s 的所有子集 _s,
若 A不在 _s里,则表明 A所在的联通图 s - _s 与 _s 没有联通,则这是一类非法方案,
即 f[s]-=f[s-_s]*g[_s]。
最后答案就是 f[全集]。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define _ %mod #define filein(x) freopen(#x".in","r",stdin); #define fileout(x) freopen(#x".out","w",stdout); using namespace std; const int mod=1000000007; int c[25][25],f[(1<<16)+10],g[(1<<16)+10]; int n; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&c[i][j]); for(int s=1;s<1<<n;s++){ g[s]=1; for(int i=0;i<n;i++) if((1<<i)&s) for(int j=i;j<n;j++) if((1<<j)&s) g[s]=1ll*g[s]*(c[i+1][j+1]+1)_; } for(int s=1;s<1<<n;s++){ f[s]=g[s]; int ii=s&-s; for(int _s=s;_s;_s=(_s-1)&s) if(~_s&ii) f[s]=((f[s]-1ll*f[s-_s]*g[_s]_+mod)_)_; } printf("%d",f[(1<<n)-1]); return 0; }
Do not go gentle into that good night.
Rage, rage against the dying of the light.
————Dylan Thomas