bzoj4671: 异或图
感觉跟这题很像bzoj2839: 集合计数
首先这个和子集没有关系,但和点的划分有关
考虑枚举点的划分,设gi表示把点分成至少分成i个块的方案数,fi去掉至少
和那题类似的想法,对于大小为x的划分,它对gi的贡献为S2(i,x)
所以有gi=sigema(1~i)x S2(i,x)*fi
上斯特林反演
fi=sigema(1~i)x (-1)^(i-x) * S1(i,x) * gi
只需要算f1,即算 sigema(1~i)x (-1)^(i-1) * S1(i,1) * gi = sigema(1~i)x (-1)^(i-1) * (i-1)! * gi
所以就是算gi就可以了,爆搜划分,把必须要不联通的搞出来,对于每个图是一个异或方程,解异或方程组,计算解的方案数即自由元个数,这里没必要高斯消元,用线性基判断就可以了
注意假如是nm^2枚举边和点check会T,要加上时间戳,一个是在线性基里这个基是否存在,一个是当前这条边是否必须要不联通,这样下来复杂度就被卡到m^2+n*m,勉强过了,bzoj中游水准
注意斯特林反演x的下标从0从1开始都没有区别,因为斯特林数除了0,0是1其他i,0都是0 ORZ%%%%zory神犇
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<bitset> using namespace std; typedef long long LL; const int maxn=110; const int maxm=20; LL fac[maxm]; void pre(){fac[0]=1;for(int i=1;i<=maxm;i++)fac[i]=fac[i-1]*i;} //-------------------------------------------def---------------------------------------------------------- int n,m,len;bitset<maxm*maxm>mp[maxn],lt[maxm*maxm],p; int tim,v[maxm*maxm],ti[maxm*maxm]; bool insert(int t) { p=mp[t]; for(int i=len;i>=1;i--) if(p[i]==1&&v[i]==tim) { if(ti[i]!=tim) { ti[i]=tim; lt[i]=p; return true; } else p^=lt[i]; } return false; } //------------------------------------------线性基-------------------------------------------------------- int num,b[maxm];LL ans; void dfs(int k) { if(k==m+1) { len=0; tim++; for(int i=1;i<=m;i++) for(int j=i+1;j<=m;j++) { len++; if(b[i]!=b[j])v[len]=tim; } int sum=0; for(int t=1;t<=n;t++) sum+=(insert(t)==false); ans+=(num%2==0?-1:1)*fac[num-1]*(1LL<<sum); return ; } for(int i=1;i<=num;i++) { b[k]=i; dfs(k+1); } b[k]=++num; dfs(k+1); num--; } char ss[maxm]; int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); pre(); scanf("%d",&n); for(int t=1;t<=n;t++) { scanf("%s",ss+1),m=strlen(ss+1); for(int i=1;i<=m;i++)mp[t][i]=(ss[i]=='1'); } for(int i=2;i<=10;i++)if(m==i*(i-1)/2){m=i;break;} dfs(1); printf("%lld\n",ans); return 0; }
pain and happy in the cruel world.