BZOJ 4596: [Shoi2016]黑暗前的幻想乡 矩阵树定理 容斥原理

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

Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 429  Solved: 244
[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

KuribohG神犇说过,看到计数想容斥

ans = 总方案数 - 一个公司不修路的方案 + 两个公司不修路的方案......

YY一下就知道是对的啦

过程中的方案数每次重构基尔霍夫矩阵用矩阵树定理求


这复杂度想想就很爆炸哈哈哈

#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<complex>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}
void print(ll x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=25,M=410,mod=int(1e9)+7;

int a[N][N],t[N][N];
int b[N],hved[N][M][2];

int det(int n)
{//cout<<n<<endl;
	register int i,j,k,x,y,tmp,res=1,f=1;
	for(i=1;i<=n;++i)for(j=1;j<=n;++j)a[i][j]=(a[i][j]%mod+mod)%mod;//cout<<a[i][j]<<" ";cout<<endl;}{
	for(i=1;i<=n;++i)
	{
		for(j=i+1;j<=n;++j)
		{
			x=a[i][i];y=a[j][i];
			while(y)
			{
				tmp=x/y;x%=y;swap(x,y);f=-f;
				for(k=i;k<=n;++k)
				a[i][k]=((1ll*a[i][k]-1ll*tmp*a[j][k])%mod+mod)%mod;
				for(k=i;k<=n;++k)swap(a[j][k],a[i][k]);
			}
		}
		if(!a[i][i])return 0;
		res=1ll*res*a[i][i]%mod;
	}
	if(f==-1)res=(mod-res)%mod;res=(res%mod+mod)%mod;
	return res;
}

int n,m,st[N],ans;

inline void update(int aim)
{
	register int i,j,k,x,y;
	for(i=1;i<=n;++i)for(j=1;j<=n;++j)a[i][j]=t[i][j];
	for(i=1;i<=aim;++i)
	{
		k=st[i];
		for(j=1;j<=b[k];j++)
		{
			x=hved[k][j][0];y=hved[k][j][1];
			a[x][y]++;a[y][x]++;
			a[x][x]--;a[y][y]--;
		}
	}
	if(aim&1)ans=((ans-det(n-1))%mod+mod)%mod;
	else ans=(ans+det(n-1))%mod;
	
}

void dfs(int step,int aim,int k)
{
	if(step>aim)
	{
		update(aim);
		return ;
	}
	for(int i=1+k;i<n;++i)
	{
		if(step+n-i-1<aim)return ;
		st[step]=i;
		dfs(step+1,aim,i);
	}
}

int main()
{
	n=read();
	register int i,j,k,x,y;
	for(i=1;i<n;++i)
	{
		b[i]=read();
		for(j=1;j<=b[i];++j)
		{
			x=hved[i][j][0]=read();
			y=hved[i][j][1]=read();
			a[x][x]++;a[x][y]--;
			a[y][y]++;a[y][x]--;
		}
	}
	for(i=1;i<n;i++)for(j=1;j<=n;j++)t[i][j]=a[i][j];
	ans=det(n-1);
	for(i=1;i<=n;++i)dfs(1,i,0);
	print(ans);puts("");return 0;
}
/*
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

17
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值