BZOJ4596:[SHOI2016]黑暗前的幻想乡——题解

https://www.lydsy.com/JudgeOnline/problem.php?id=4596

https://www.luogu.org/problemnew/show/P4336#sub

四年一度的幻想乡大选开始了,最近幻想乡最大的问题是很多来历不明的妖怪涌入了幻想乡,扰乱了幻想乡昔日的秩序。但是幻想乡的建制派妖怪(人类)博丽灵梦和八云紫等人整日高谈所有妖怪平等,幻想乡多元化等等,对于幻想乡目前面临的种种大问题却给不出合适的解决方案。
风间幽香是幻想乡里少有的意识到了问题的严重性的大妖怪。她这次勇敢的站了出来参加幻想乡大选。提出包括在幻想乡边境建墙(并让人类出钱),大力开展基础设施建设挽回失业率等一系列方案,成为了大选年出人意料的黑马并顺利的当上了幻想乡的大统领。
 
幽香上台以后,第一项措施就是要修建幻想乡的公路。幻想乡有 N 个城市,之间原来没有任何路。幽香向选民承诺要减税,所以她打算只修 N- 1 条路将这些城市连接起来。但是幻想乡有正好 N- 1 个建筑公司,每个建筑公司都想在修路的过程中获得一些好处。虽然这些建筑公司在选举前没有给幽香钱,幽香还是打算和他们搞好关系,因为她还指望他们帮她建墙。所以她打算让每个建筑公司都负责一条路来修。
 
每个建筑公司都告诉了幽香自己有能力负责修建的路是哪些城市之间的。所以幽香打算选择 N-1 条能够连接幻想乡所有城市的边,然后每条边都交给一个能够负责该边的建筑公司修建,并且每个建筑公司都恰好修一条边。
 
幽香现在想要知道一共有多少种可能的方案呢?两个方案不同当且仅当它们要么修的边的集合不同,要么边的分配方式不同。

看了矩阵树教程它让我做这道题http://www.cnblogs.com/zj75211/p/8039443.html

深感自己老龄化严重。

n<=17显然是要压状态了,那我们只能压是否选择每个公司来建边了……好像和题意不符啊。

但是思考,事实上我们要求的东西可以通过容斥来表达出来,即(不考虑哪些公司建哪些边)我加所有公司的边的方案数-我少一个公司的所有边的方案数+……最后反正是把所有不合法的方案都剔除出去了。

那么我们枚举状态,用矩阵树定理求出生成树个数就是方案了,之后再加加减减就行了。

#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<cctype>
#include<algorithm>
using namespace std;
typedef long long ll;
const int p=1e9+7;
const int N=18;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
ll x[N][N],ans;
ll gauss(int n){
    ll res=1;
    for(int i=1;i<=n;i++){
    for(int j=i+1;j<=n;j++){
        while(x[j][i]!=0){
        ll t=x[i][i]/x[j][i];
        for(int k=i;k<=n;k++)x[i][k]=(x[i][k]-x[j][k]*t)%p;
        for(int k=i;k<=n;k++)swap(x[i][k],x[j][k]);
        res=-res;
        }
    }
    if(!x[i][i])return 0;
    res=(res*x[i][i])%p;
    }
    return (res%p+p)%p;
}
int n,m[N],u[N][N*N],v[N][N*N],d[N];
int main(){
    n=read();
    for(int i=1;i<n;i++){
    m[i]=read();
    for(int j=1;j<=m[i];j++){
        u[i][j]=read(),v[i][j]=read();
    }
    }
    for(int i=(1<<(n-1))-1;i>=0;i--){
    int tot=0;
    memset(x,0,sizeof(x));
    memset(d,0,sizeof(d));
    for(int j=1;j<n;j++){
        int w=(i>>(j-1))&1;
        if(w){
        for(int k=1;k<=m[j];k++){
            x[u[j][k]][v[j][k]]--;
            x[v[j][k]][u[j][k]]--;
            d[u[j][k]]++;d[v[j][k]]++;
        }
        for(int k=1;k<=n;k++)x[k][k]=d[k];
        tot++;
        }
    }
    ll tmp=gauss(n-1);
    if((n-1-tot)%2==0)ans=(ans+tmp)%p;
    else ans=(ans-tmp)%p;
    }
    printf("%lld\n",(ans%p+p)%p);
    return 0;
}

+++++++++++++++++++++++++++++++++++++++++++

+本文作者:luyouqi233。               +

+欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/ +

+++++++++++++++++++++++++++++++++++++++++++

posted @ 2018-05-02 16:10  luyouqi233  阅读(220)  评论(0编辑  收藏  举报