【bzoj4596】[Shoi2016]黑暗前的幻想鄉(狀壓+容斥原理+矩陣樹定理+高斯消元)

Description
四年一度的幻想鄉大選開始了,最近幻想鄉最大的問題是很多來歷不明的妖

怪湧入了幻想鄉,擾亂了幻想鄉昔日的秩序。但是幻想鄉的建制派妖怪(人類)

博麗靈夢和八雲紫等人整日高談所有妖怪平等,幻想鄉多元化等等,對於幻想鄉

目前面臨的種種大問題卻給不出合適的解決方案。

風間幽香是幻想鄉里少有的意識到了問題的嚴重性的大妖怪。她這次勇敢的

站了出來參加幻想鄉大選。提出包括在幻想鄉邊境建牆(並讓人類出錢),大力

開展基礎設施建設挽回失業率等一系列方案,成爲了大選年出人意料的黑馬並順

利的當上了幻想鄉的大統領。

幽香上臺以後,第一項措施就是要修建幻想鄉的公路。幻想鄉有 N 個城市,

之間原來沒有任何路。幽香向選民承諾要減稅,所以她打算只修 N- 1 條路將

這些城市連接起來。但是幻想鄉有正好 N- 1 個建築公司,每個建築公司都想

在修路的過程中獲得一些好處。

雖然這些建築公司在選舉前沒有給幽香錢,幽香還是打算和他們搞好關係,

因爲她還指望他們幫她建牆。所以她打算讓每個建築公司都負責一條路來修。

每個建築公司都告訴了幽香自己有能力負責修建的路是哪些城市之間的。所

以幽香打算選擇 N-1 條能夠連接幻想鄉所有城市的邊,然後每條邊都交給一

個能夠負責該邊的建築公司修建,並且每個建築公司都恰好修一條邊。

幽香現在想要知道一共有多少種可能的方案呢?兩個方案不同當且僅當它

們要麼修的邊的集合不同,要麼邊的分配方式不同。

Input
第一行包含一個正整數 N(N<=17), 表示城市個數。

接下來 N-1 行,其中第 i行表示第 i個建築公司可以修建的路的列表:

以一個非負數mi 開頭,表示其可以修建 mi 條路,接下來有mi 對數,

每對數表示一條邊的兩個端點。其中不會出現重複的邊,也不會出現自環。

Output
僅一行一個整數,表示所有可能的方案數對 10^9 + 7 取模的結果。

Sample Input
4
2 3 2 4 2
5 2 1 3 1 3 2 4 1 4 3
4 2 1 3 2 4 1 4 2

Sample Output
17

這道題的解法十分暴力。
我們發現n 的範圍很小,所以我們可以考慮狀壓。
每一個狀態i 表示當前計算的生成樹數量包含哪些顏色
根據容斥原理可得:最後的答案就是:任取 一種顏色不選+ 兩種顏色不選 ……
統計每個狀態的答案用矩陣樹定理+高斯消元即可
時間複雜度O(2nn3)
如果有誤在評論區吼一聲哦!
代碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=1e9+7;
int n,ans,m[20],edge[410][410][2],a[20][20];
int qpow(int x,int n){
    int ret=1;
    while(n){
        if(n&1)
            ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;
        n>>=1;
    }
    return ret;
}
int gauss(int n){
    int ret=1;
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            if(!a[i][i])
                return 0;
            if(!a[j][i])
                continue;
            int tmp=1ll*a[j][i]*qpow(a[i][i],mod-2)%mod;
            for(int k=i;k<=n;k++){
                a[j][k]-=1ll*a[i][k]*tmp%mod;
                if(a[j][k]<0)
                    a[j][k]+=mod;
            }
        }
    }
    for(int i=1;i<=n;i++)
        ret=1ll*ret*a[i][i]%mod;
    return ret;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d",m+i);
        for(int j=1;j<=m[i];j++)
            scanf("%d%d",edge[i][j],edge[i][j]+1);
    }
    for(int i=0;i<(1<<(n-1));i++){
        memset(a,0,sizeof(a));
        int f=1;
        for(int j=1;j<n;j++){
            if(i&(1<<(j-1))){
                for(int k=1;k<=m[j];k++){
                    a[edge[j][k][0]][edge[j][k][1]]--;
                    a[edge[j][k][1]][edge[j][k][0]]--;
                    a[edge[j][k][0]][edge[j][k][0]]++;
                    a[edge[j][k][1]][edge[j][k][1]]++;
                }
            }
            else f=-f;
        }
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                if(a[j][k]<0)
                    a[j][k]+=mod;
        ans+=f*gauss(n-1);
        if(ans<0)
            ans+=mod;
        if(ans>=mod)
            ans-=mod;
    }
    printf("%d",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章