[bzoj4596][矩阵树][容斥]黑暗前的幻想乡

4 篇文章 0 订阅
2 篇文章 0 订阅

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

Time Limit: 20 Sec Memory Limit: 256 MB
Submit: 565 Solved: 328
[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
HINT

Source

By 佚名上传

sol:

生成树计数嘛,显然矩阵树咯。但是要求一个建筑公司只能修一条路的话,我们容斥一下就好了,枚举有什么建筑公司被我们选择了就行。复杂度 2nn3 2 n ∗ n 3
讲道理近6e的复杂度为啥能过啊。。而且下面我有一点剪枝,去了剪枝更快了?雾

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
typedef double s64;
int n,m;
inline int read()
{
    char c;
    int res,flag=0;
    while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
    res=c-'0';
    while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
    return flag?-res:res;
}
const int N=20;
struct cc
{
    int x,y;
};
vector<cc> q[N];
int c[N][N];
const int pyz=1e9+7;
int ans=0;
inline int ksm(int s,int t)
{
    int res=1;
    while(t)
    {
        if(t&1) res=(ll)res*s%pyz;
        s=(ll)s*s%pyz;
        t>>=1;
    }
    return res;
}
int o;
inline void guass()
{
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
    c[i][j]=(c[i][j]%pyz+pyz)%pyz;
    m=n-1;
    int tmp=o;
    for(int i=1;i<=m;++i)
    {
        int row;
        for(row=i;row<=m;++row) if(c[row][i]) break;
        if(row>m) return;
        if(row!=i)
        {
            for(int j=1;j<=m;++j) swap(c[row][j],c[i][j]);
            tmp*=-1;
        }
        int inv=ksm(c[i][i],pyz-2);
        for(int j=i+1;j<=m;++j)
        if(c[j][i])
        {
            int tmp=(ll)c[j][i]*inv%pyz;
            for(int k=1;k<=m;++k)
            c[j][k]=(c[j][k]+pyz-(ll)tmp*c[i][k]%pyz)%pyz;
        }
    }
    tmp=(tmp+pyz)%pyz;
    for(int i=1;i<=m;++i)
    tmp=(ll)tmp*c[i][i]%pyz;
    ans=(ans+tmp)%pyz;
/*  for(int i=1;i<=m;++i){
    for(int j=1;j<=m;++j)
        printf("%d ",c[i][j]);
    printf("\n");}*/
}
int main()
{
//  freopen("4596.in","r",stdin);
//  freopen(".out","w",stdout);
    n=read();
    for(int i=1;i<n;++i)
    {
        int k;
        k=read();
        cc t;
        for(int j=1;j<=k;++j)
        {
            t.x=read();
            t.y=read();
            q[i].push_back(t);
        }
    }
    int all=1<<n-1;
    --all;
    vector<cc>::iterator it;
    for(int op=1;op<=all;++op)
//  int op=all;
    {
        o=1;
        memset(c,0,sizeof(c));
        int cs=0;
        for(int i=1;i<n;++i)
        if((1<<i-1)&op)//说明我们用这个建筑公司
        {
            for(it=q[i].begin();it!=q[i].end();++it)
            {
                ++cs;
                c[(*it).x][(*it).y]--;
                c[(*it).y][(*it).x]--;
                c[(*it).x][(*it).x]++;
                c[(*it).y][(*it).y]++;
            }
        }
        else o*=-1;
        if(cs<n) continue;
        bool flag=0;
        for(int i=1;i<=n;++i)
        if(!c[i][i]) flag=1;
        if(flag) continue;
        guass();
    }
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值