bzoj 4671 異或圖——容斥+斯特林反演+線性基
題目:https://www.lydsy.com/JudgeOnline/problem.php?id=4671
考慮計算不是連通圖的方案,乘上容斥係數來進行容斥。
可以列舉子集劃分(複雜度是O(Bell))。就是 dfs ,記錄已經有了幾個集合,列舉當前元素放在哪個集合裡(給它標一個 id )或者當前元素自己開一個集合。
然後就有了限制:不同點集之間不能有邊。本來想限制同一點集必須是連通的,但不好限制,所以就不限制了,把這部分的影響算在容斥係數裡。
如果限制不同點集之間不能有邊,可以考慮高斯消元。有 k 條邊有限制的話,就寫出 k 個方程,解出自由元的個數 d ,2d 就可以加入答案。
不過線性基更好寫。
在這道題裡,可以算每個圖的 nw 值, nw 的第 i 位是1表示第 i 條邊限制不能選,而且這個圖有第 i 條邊;其餘情況的話這個圖選不選對於第 i 條邊是否合法沒有影響(也可以是第 i 條邊沒有限制,所以其合法性自然不會受到任何圖選不選的影響),第 i 位上的值就是 0 。這個 nw 只要把 “有限制的邊的位是1” 的那個 long long 和 “這個圖有的邊的位是1” 的那個 long long & 一下就行了。
然後合法的子集選取方案需要滿足選中的圖的 nw 異或起來是0。所以對這些 nw 求一個線性基,設線性基大小為 k 、一共 m 個圖,則方案數為 2m-k
這樣就求出了 “至少有 i 個連通塊” 的方案數 w[ i ] 。考慮怎麼用它求出 “恰好有 i 個連通塊” 的方案數 g[ i ] 。
設容斥係數為 f[ i ] 。統計答案的時候,有 \( ans=\sum\limits_{i=0}^{n}w[j]*f[j] \)
對於 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; }