bzoj 4671 异或图——容斥+斯特林反演+线性基
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4671
考虑计算不是连通图的方案,乘上容斥系数来进行容斥。
可以枚举子集划分(复杂度是O(Bell))。就是 dfs ,记录已经有了几个集合,枚举当前元素放在哪个集合里(给它标一个 id )或者当前元素自己开一个集合。
然后就有了限制:不同点集之间不能有边。本来想限制同一点集必须是连通的,但不好限制,所以就不限制了,把这部分的影响算在容斥系数里。
如果限制不同点集之间不能有边,可以考虑高斯消元。有 k 条边有限制的话,就写出 k 个方程,解出自由元的个数 d ,2d 就可以加入答案。
不过线性基更好写。https://www.cnblogs.com/ljh2000-jump/p/5869991.html
在这道题里,可以算每个图的 nw 值, nw 的第 i 位是1表示第 i 条边限制不能选,而且这个图有第 i 条边;其余情况的话这个图选不选对于第 i 条边是否合法没有影响(也可以是第 i 条边没有限制,所以其合法性自然不会受到任何图选不选的影响),第 i 位上的值就是 0 。这个 nw 只要把 “有限制的边的位是1” 的那个 long long 和 “这个图有的边的位是1” 的那个 long long & 一下就行了。
然后合法的子集选取方案需要满足选中的图的 nw 异或起来是0。所以对这些 nw 求一个线性基,设线性基大小为 k 、一共 m 个图,则方案数为 2m-k ,因为不在线性基里的图可以任意选,选好它们后异或出来的结果可以通过线性基里的唯一一种选法来调成0。
这样就求出了 “至少有 i 个连通块” 的方案数 w[ i ] 。考虑怎么用它求出 “恰好有 i 个连通块” 的方案数 g[ i ] 。
设容斥系数为 f[ i ] 。统计答案的时候,有 \( ans=\sum\limits_{i=0}^{n}w[i]*f[i] \)
对于 g[ m ] 来说,在 w[ i ] 里包含了 S( m,i ) 个 g[ m ] 。所以 g[ m ] 会被加到答案里 \( \sum\limits_{i=0}^{n}S(m,i)*f[i] \) 次。
现在想要的效果是选取了合适的 f[ ] ,使得求好的 ans 里只包含了 1 个 g[ 1 ] 。
即: \( \sum\limits_{i=0}^{n}S(m,i)*f[i] = [ m=1 ] \)
设 \( h(m) = [ m=1 ] \) ,则 \( h[m]=\sum\limits_{i=0}^{n}S(m,i)*f[i] \)
因为 S( i , j ) = 0 ( j>i ) ,所以也就是 \( h[m]=\sum\limits_{i=0}^{m}S(m,i)*f[i] \)
这样就是斯特林反演的形式了。于是有 \( f[m]=\sum\limits_{i=0}^{m}(-1)^{m-i}*s(m,i)*h[i] \)
只有 i=1 时 h 的值是1,所以就是 \( f[m]=(-1)^{m-1}*(m-1)! \) (\( s(m,i)=\frac{m!}{m}=(m-1)! \))
这样就得到了容斥系数,就可以统计答案啦!
之所以这里的容斥系数不是那种 (-1)k 了,是因为那种系数适用于 “至少一个连通块” = “恰好一个连通块”+“恰好两个连通块+ ... ,而这里是:“至少一个连通块” = “恰好一个连通块 * S(1,1)”+“恰好两个连通块 * S(2,1)” + ... 。
注意到处开 long long 。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define ll long long using namespace std; const int N=65,M=50,K=15; int n,m,f[K],id[K]; ll ans,bin[N],b[N],base[M]; void init() { scanf("%d",&m); char ch[M]; scanf("%s",ch); int len=strlen(ch); n=(1+sqrt(1+8*len))/2; f[0]=1;for(int i=1;i<=n;i++)f[i]=f[i-1]*i; for(int i=n;i;i--)f[i]=((i-1)&1?-1:1)*f[i-1]; bin[0]=1;for(int i=1,j=max(m,len-1);i<=j;i++)bin[i]=bin[i-1]<<1;//max(m,len-1) for(int i=0;i<len;i++)b[1]|=(ch[i]=='1')?bin[i]:0; for(int i=2;i<=m;i++) { scanf("%s",ch); for(int j=0;j<len;j++)b[i]|=(ch[j]=='1')?bin[j]:0; } } void dfs(int cr,int cnt) { if(cr>n) { ll t=0;int bh=0;//ll for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++,bh++) t|=(id[i]==id[j])?0:bin[bh]; int tot=0; for(int k=0;k<=bh;k++)base[k]=0;//<=bh for(int i=1;i<=m;i++) { ll nw=b[i]&t; for(int k=0;k<=bh;k++)//bh if(nw&bin[k]) { if(!base[k]){base[k]=nw;tot++;break;} nw^=base[k]; } } ans+=bin[m-tot]*f[cnt]; return; } for(int i=1;i<=cnt;i++) id[cr]=i,dfs(cr+1,cnt); id[cr]=cnt+1; dfs(cr+1,cnt+1); } int main() { init(); dfs(1,0); printf("%lld\n",ans); return 0; }