bzoj 4596

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

Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 257  Solved: 152
[Submit][Status][Discuss]

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
详细地讲一下
不要问为什么,这道题用容斥原理+矩阵树定理
1.矩阵树定理可以求一个单位权图的生成树的个数,这个东西可以自己上网找一下。
2.容斥原理:
首先我们想一下,题目要求每个公司选一条自己能建的边,那么很好啊,我们把每个人的边都加进去,跑矩阵树定理,不就出来了吗?当然不了,因为可能出现某个人的边没被选。这很讨厌,那么我们就要减去这些情况,这些情况都包含什么情况呢?假如一号公司的边没被选中,那么这些情况是要减掉的,不如我们把一号公司的边全删掉,再跑矩阵树定理,加上,然后把二号公司的边都删去,然后。。。是不是就删去了所有那些有人没选的情况?不,这样删多了,因为我们去掉一号公司的边,跑矩阵树定理,也把二号公司没选的方案减了,跑二号公司时,这些情况也减去了,就减多了,那么怎么加上这些情况呢?不如我们把一号公司二号公司的边全删掉,再跑。。。一直这样,这就是容斥原理。。。
2^n枚举一个状态,一个位是一表示这个公司的所有边都加进去了,如果公司%2的数量和总数%2相等,就加上,否则减去。
记住数组要开大,边数有17*16/2种。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 30
#define mod 1000000007
struct edge
{
    int u[1010],v[1010],size;
}x[N];
int n;
ll ans;
ll a[N][N],d[N][N],g[N][N];
ll gauss()
{
    ll f=1,ret=1;
    for(int i=1;i<=n;++i)
    {
        for(int j=i+1;j<=n;++j)
        {
            ll A=g[i][i],B=g[j][i];
            while(B)
            {
                ll t=A/B; A%=B; swap(A,B);
                for(int k=i;k<=n;++k) 
                    g[i][k]=((g[i][k]-t*g[j][k]%mod)%mod+mod)%mod;
                for(int k=i;k<=n;++k) swap(g[i][k],g[j][k]);
                f=-f;
            }
        }   
        if(!a[i][i]) return 0;
    }
    for(int i=1;i<=n;++i) ret=ret*g[i][i]%mod;
    ret=(ret*f%mod+mod)%mod;
    return ret;
}
void solve()
{
    for(int i=1;i<1<<n;++i)
    {
        memset(a,0,sizeof(a));
        memset(g,0,sizeof(g));
        memset(d,0,sizeof(d));
        int tot=0;
        for(int j=1;j<=n;++j) if(i&(1<<(j-1)))
        {
            ++tot;
            for(int k=1;k<=x[j].size;++k)
            { 
                int u=x[j].u[k],v=x[j].v[k];
                a[u][u]++; a[v][v]++;
                d[u][v]++; d[v][u]++;
            }
        }
        for(int j=1;j<=n;++j)
            for(int k=1;k<=n;++k) g[j][k]=a[j][k]-d[j][k];
        if(n%2==tot%2) ans+=gauss();
        else ans-=gauss();
        ans=(ans%mod+mod)%mod;
    }
    ans=(ans%mod+mod)%mod;
    printf("%lld\n",ans);
}
int main()
{
    scanf("%d",&n);
    --n;
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&x[i].size);
        for(int j=1;j<=x[i].size;++j) scanf("%d%d",&x[i].u[j],&x[i].v[j]);
    }   
//  if(!n)
//  {
//      puts("1");
//      return 0;
//  }
    solve();
    return 0;
}
View Code

 

 
posted @ 2017-02-17 17:58  19992147  阅读(186)  评论(0编辑  收藏  举报