1. 程式人生 > >[bzoj4671]異或圖——容斥+斯特林數反演+線性基

[bzoj4671]異或圖——容斥+斯特林數反演+線性基

題目大意:

定義兩個結點數相同的圖 G1 與圖 G2 的異或為一個新的圖 G, 其中如果 (u, v) 在 G1 與G2 中的出現次數之和為 1, 那麼邊 (u, v) 在 G 中, 否則這條邊不在 G 中.
現在給定 s 個結點數相同的圖 G1...s, 設 S = {G1, G2, . . . , Gs}, 請問 S 有多少個子集的異或為一個連通圖?

思路:

這種計算連通圖的個數的題目一般情況下考慮容斥。
由於一個圖的聯通不好直接計算,但是對於點集的劃分,我們要使它不連通會容易得多。
於是在發現雖然\(s\)較大,但是\(n\)很小的情況下,我們可以列舉這\(n\)個點的劃分,然後對於每一個劃分,強制不同集合中的點不連通,同一個集合中的點任意,然後計算滿足條件的圖的個數。
這個時候我們的容斥係數應該滿足這樣的條件,對於一個擁有\(m\)

個聯通塊的圖,需要滿足
\[ \sum_{i=1}^{m}{m\brace i}\times f_i=[m=1] \]
然後考慮直接用斯特林數反演來求出\(f_i:\)
\[ f_i=(-1)^{(i-1)}\times (i-1)! \]
接下來考慮如何計算滿足各個集合中的點不連通的方案數,這裡把跨越集合的邊單獨提出來,每條邊的存在狀況可以看成是一個\(01\)串,現在即求這\(s\)個01串的異或和中有多少個\(0\)
於是我們可以直接對這\(s\)個數建立線性基,如果最後線性基中有\(c\)個元素,那麼一共有\(2^{s-c}\)種方式可以使異或和為0。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj4671.in","r",stdin);
    freopen("bzoj4671.out","w",stdout);
}

template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}

const int maxn=10+10;
const int maxs=60+10;
int s,n,bel[maxn],q[maxs],cnt;
char str[maxs][maxs];
ll num[maxs],fac[maxs],f[maxs],ans,b[maxs];

void init(){
    read(s);
    REP(i,1,s)scanf("%s",str[i]+1);
    int len=strlen(str[1]+1);
    REP(i,2,10)if(i*(i-1)/2==len)n=i;

    fac[0]=1;
    REP(i,1,10)fac[i]=fac[i-1]*i;
    REP(i,1,10)f[i]=((i-1)%2 ? -1 : 1)*fac[i-1];
}

void calc(int tot){
    REP(i,1,s){
        num[i]=0;
        REP(j,1,cnt)num[i]=num[i]<<1|(str[i][q[j]]^'0');
    }
    REP(i,1,cnt)b[i]=0;
    int c=0;
    REP(i,1,s){
        DREP(j,cnt,1){
            if((1ll<<(j-1))&num[i]){
                if(!b[j]){++c;b[j]=num[i];break;}
                num[i]^=b[j];
            }
        }
    }
    ans+=f[tot]*(1ll<<(s-c));
}

void dfs(int k,int tot){
    if(k>n){
        cnt=0;
        int id=0;
        REP(i,1,n)REP(j,i+1,n){
            ++id;
            if(bel[i]!=bel[j])
                q[++cnt]=id;
        }
        calc(tot);
        return;
    }
    bel[k]=tot+1;
    dfs(k+1,tot+1);
    REP(i,1,tot){
        bel[k]=i;
        dfs(k+1,tot);
    }
}

int main(){
    File();
    init();
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}