1. 程式人生 > 其它 >【題解】[SHOI2016]黑暗前的幻想鄉

【題解】[SHOI2016]黑暗前的幻想鄉

[SHOI2016]黑暗前的幻想鄉

少有的自己逐漸發現了容斥思路的題……

題目大意:要求原無向圖的生成樹個數,滿足每條邊被建立的公司不同。

\(\text{Solution:}\)

觀察到了我們有 \(n-1\) 個公司,所以在題目的限制下,滿足條件的生成樹恰好所有公司都參與。所以我們不需要考慮每條邊了,直接考慮:對當前情況下 \(x\) 個公司參與的生成樹個數 。這樣只要我們求出對應 \(n-1\) 情況下的答案,那它直接就是答案了。

那麼回到原來的問題,為什麼直接忽略公司對原圖跑 Matrix-Tree 定理不對呢?這樣的答案中 包含了許多 \(n-1\) 個公司中的子集。 那麼思路就變成了:如何把這些不合法的子集去掉呢?結合 \(n\leq 17\)

的資料範圍也就自然想到了狀壓列舉子集的容斥思路。

仔細思考一下,我們想要列舉一些公司使得它們 每一個 都要參與到生成樹中。但實際上這個問題也是困難的,那不妨先擱置一下,來想一想容斥的想法:

顯然我們可以用總的減去算重的:列舉參加了多少公司,則:

\[Ans=\sum_{n-1}f_i-\sum_{n-2}f_i+\sum_{n-3}f_i... \]

容斥係數就和列舉到的公司數奇偶性有關係。

那麼,這個式子是對於強制了多少公司都參與的情況下成立的,剛剛我們說即使這樣算,算到的結果也包含了對應這些公司的子集。那麼怎麼算呢?

其實按照上面這個式子上去算也是對的,因為在每一步加減的過程中,比當前列舉到的更小的子集都被抵消掉了,不理解的話可以手推一下,由於恰好抵消,所以我們直接這樣列舉容斥就是對的。

於是我們就可以套用矩陣樹定理了。注意到有重邊的情況,由於這種重邊一定來自於兩個不同的公司,所以我們不能把它刪掉,直接加進去就是對的。

順便再提一句矩陣樹定理:基爾霍夫矩陣是用 度數矩陣 減去 鄰接矩陣,求出的結果是:所有生成樹邊權之積的和。

那麼求方案數的時候,只需要令乘積為 \(1\to\) 令邊權為 \(1\) 即可。

複雜度 \(O(2^n\cdot n^3),\) 跑的還是蠻快的。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=20;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Dec(int x,int y){return (x-y+mod)%mod;}
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline int Abs(int x){if(x<0)x=-x;return x;}
inline int qpow(int x,int y){
    int res=1;
    while(y){
        if(y&1)res=Mul(res,x);
        x=Mul(x,x);y>>=1;
    }
    return res;
}
inline int getinv(int x){return qpow(x,mod-2);}
inline int read(){
    int s=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch)){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s;
}
typedef pair<int,int> pr;
#define fi first
#define se second
#define mp make_pair
int n,m[N];
vector<pr>edge[N];
struct Matrix{
    int a[N][N];
    Matrix(){memset(a,0,sizeof a);}
    int MatrixTree(int L){
        int res=1;
        for(int i=L;i<=n;++i){
            int pos=i;
            for(int j=i+1;j<=n;++j)
                if(a[pos][i]<a[j][i])
                    pos=j;
            if(i!=pos)res=Mul(res,mod-1),swap(a[i],a[pos]);
            int div=getinv(a[i][i]);
            res=Mul(res,a[i][i]);
            for(int j=i;j<=n;++j)a[i][j]=Mul(a[i][j],div);
            for(int j=i+1;j<=n;++j){
                div=a[j][i];
                for(int k=i;k<=n;++k)a[j][k]=Dec(a[j][k],Mul(div,a[i][k]));
            }
        }
        for(int i=L;i<=n;++i)res=Mul(res,a[i][i]);
        return res;
    }
};
int f[1<<N];
Matrix GetMatrix(int state){
    Matrix A;
    for(int i=0;i<n-1;++i){
        if(!(state>>i&1))continue;
        for(auto j:edge[i]){
            int u=j.fi;
            int v=j.se;
            A.a[u][u]=Add(A.a[u][u],1);
            A.a[v][v]=Add(A.a[v][v],1);
            A.a[u][v]=Dec(A.a[u][v],1);
            A.a[v][u]=Dec(A.a[v][u],1);
        }
    }
    return A;
}
inline int lowbit(int x){return x&(-x);}
inline int popcount(int x){
    int res=0;
    while(x){
        res++;
        x-=lowbit(x);
    }
    return res;
}
int sum[N],Ans;
int main(){
    n=read();
    for(int i=0;i<n-1;++i){
        m[i]=read();
        for(int j=0;j<m[i];++j){
            int u=read(),v=read();
            edge[i].push_back(mp(u,v));
        }
    }
    for(int i=0;i<(1<<(n-1));++i){
        Matrix A=GetMatrix(i);
        f[i]=A.MatrixTree(2);
    }
    for(int i=0;i<(1<<(n-1));++i){
        int num=popcount(i);
        sum[num]=Add(sum[num],f[i]);
    }
    int opt=(n-1)&1;
    for(int i=n-1;i>=1;--i){
        int op=i&1;
        if(op==opt)Ans=Add(Ans,sum[i]);
        else Ans=Dec(Ans,sum[i]);
    }
    printf("%d\n",Ans);
    return 0;
}