1. 程式人生 > >bzoj 4671 異或圖——容斥+斯特林反演+線性基

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[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;
}