bzoj 4671 异或图 —— 容斥+斯特林反演+线性基
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4671
首先,考虑容斥,就是设 \( t[i] \) 表示至少有 \( i \) 个连通块的方案数;
我们希望得到恰好有一个连通块的方案数,但这里不能直接 \( + t[1] - t[2] + t[3] - t[4] ... \),因为每个“恰好 \( i \) 个连通块”的情况并不是在各种 \( t[j] ( j<=i ) \) 中只被算了一次,而是因为标号,被算了 \( S(i,j) \) 次;
所以希望得到一个容斥系数 \( f[i] \) ,若设 \( g[m] \) 表示恰好 \( m \) 个连通块的情况,则令 \( g[m] = \sum\limits_{i=1}^{m} S(m,i) * f[i] * t[i] \)
又因为 \( ans = g[1] \),所以干脆令 \( f[i] \) 满足 \( [m=1] = \sum\limits_{i=1}^{m} S(m,i) * f[i] \),代入 \( t[i] \),算出的就是 \( g[1] \) ,即答案;
可以斯特林反演,于是 \( f[m] = \sum\limits_{i=1}^{m} s(m,i) * (-1)^{m-i} * [i=1] \),这里的 \( s(m,i) \) 是第一类斯特林数;
于是 \( f[m] = (m-1)! * (-1)^{m-1} \)
接下来问题就是求 \( t[i] \)
\( dfs \) 枚举集合划分,复杂度是 \( Bell[n] \) 的,可以接受;
枚举了集合划分后,这些集合之间一定不能有边,这与每张图的连边情况构成了一组线性方程组;
找出线性基有 \( cnt \) 个,那么 \( s-cnt \) 张图的使用是不受限制的,换句话说,即使随便使用,构成一种情况,也可以通过线性基的那些图调整成集合间没有连边的情况;
所以 \( t[集合数] \) 加上 \( 2^{s-cnt} \)
不用数组而用一个 long long 整数,再预处理2的整数次幂,可以把时间优化到bzoj上的一般水平囧。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; int s,n,m,cnt,id[15][15],col[15],jc[15],t[50]; bool sid[65][50],vis[50]; ll f[15],a[50],bin[65]; char ch[65]; ll pw(ll a,int b) { ll ret=1; for(;b;b>>=1,a=a*a)if(b&1)ret=ret*a; return ret; } void add(int x) { cnt++; a[cnt]=0; for(int i=1;i<=s;i++) if(sid[i][x])a[cnt]|=bin[i-1];//G[i]->ed[x] } int cal() { int num=0; memset(vis,0,sizeof vis); for(int i=1;i<=cnt;i++) for(int j=1;j<=s;j++) { if(!(bin[j-1]&a[i]))continue; if(vis[j])a[i]^=a[t[j]]; else {num++,vis[j]=1,t[j]=i; break;}// } return s-num; } void dfs(int x,int cr) { if(x==n+1) { cnt=0; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(col[i]!=col[j])add(id[i][j]); f[cr]+=pw(2,cal()); return; } for(int i=1;i<=cr;i++)col[x]=i,dfs(x+1,cr),col[x]=0; col[x]=cr+1; dfs(x+1,cr+1); col[x]=0; } void init() { jc[0]=1; for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i; bin[0]=1; for(int i=1;i<=s;i++)bin[i]=bin[i-1]+bin[i-1]; } int main() { scanf("%d",&s); for(int i=1;i<=s;i++) { scanf("%s",ch+1); m=strlen(ch+1); for(int j=1;j<=m;j++)sid[i][j]=ch[j]-'0'; } while(n*(n-1)/2<m)n++; for(int i=1,tmp=0;i<=n;i++) for(int j=i+1;j<=n;j++)id[i][j]=id[j][i]=++tmp;//id[j][i] init(); dfs(1,0); ll ans=0; for(int i=1;i<=n;i++) ans+=(i&1?1:-1)*jc[i-1]*f[i]; printf("%lld\n",ans); return 0; }