【BZOJ】4596: [Shoi2016]黑暗前的幻想乡

【题意】给定n个点的无向完全图,有n-1个公司各自分管一部分路,要求所有公司都有修路的生成树数。n<=17。

【算法】容斥原理+生成树计数(矩阵树定理)

【题解】每个生成树方案是一个公司有无修路的01排列,定义集合x为公司x有修路的方案集合,则题目要求集合交。

对于若干集合的集合并补集,即x个公司不修路的方案数,就是除去这x个公司的边的生成树数。

ans=Σ(-1)^k g(k),0<=k<=n-1。g(k)表示枚举k个公司不修的生成树数。

复杂度O(2^(n-1)*n^3)。

注意:

1.答案变成非负数。

2.公司边集最大N*N。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20,MOD=1e9+7;
int a[maxn][maxn],b[maxn][maxn*maxn][2],sz[maxn],n,ans;//
bool c[maxn];
void gcd(int a,int b,int &x,int &y){
    if(!b){x=1;y=0;}
    else{gcd(b,a%b,y,x);y-=x*(a/b);}
}
int inv(int a){int x,y;gcd(a,MOD,x,y);return (x%MOD+MOD)%MOD;}
int det(int n){
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=(a[i][j]+MOD)%MOD;
    bool y=0;
    for(int i=1;i<=n;i++){
        int r=i;
        for(int j=i+1;j<=n;j++)if(a[j][i]>a[r][i])r=j;
        if(r!=i){y^=1;for(int j=i;j<=n;j++)swap(a[r][j],a[i][j]);}
        int v=inv(a[i][i]);
        for(int j=i+1;j<=n;j++){
            for(int k=n;k>=i;k--){
                a[j][k]=(a[j][k]-1ll*a[j][i]*v%MOD*a[i][k]%MOD+MOD)%MOD;
            }
        }
    }
    int as=y?MOD-1:1;
    for(int i=1;i<=n;i++)as=1ll*as*a[i][i]%MOD;
    return as;
}    
void insert(int u,int v){a[u][v]--;a[v][u]--;a[u][u]++;a[v][v]++;}
int solve(){
    memset(a,0,sizeof(a));
    for(int i=1;i<n;i++)if(!c[i]){// 
        for(int j=1;j<=sz[i];j++)insert(b[i][j][0],b[i][j][1]);
    }
    return det(n-1);
}    
void dfs(int x,int y){
    if(x==n){
        ans=(ans+y*solve())%MOD;
    }
    else{
        c[x]=0;
        dfs(x+1,y);
        c[x]=1;
        dfs(x+1,-y);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d",&sz[i]);
        for(int j=1;j<=sz[i];j++)scanf("%d%d",&b[i][j][0],&b[i][j][1]);
    }
    ans=0;
    dfs(1,1);
    printf("%d",(ans+MOD)%MOD);//
    return 0;
}
View Code

 

posted @ 2017-12-30 11:29  ONION_CYC  阅读(206)  评论(0编辑  收藏  举报