[SHOI2016]黑暗前的幻想乡

题目

这题好熟练啊

看到恰好想容斥这非常熟练吧

又发现数据范围这么小,显然是允许我们\(2^n\)暴力容斥的

于是我们枚举一个二进制状态\(S\),我们只使用包含在\(S\)这个状态里的公司的边,我们这样求出的就是至少有\(n-|S|\)个公司没有修建的方案数,容斥系数显然是\((-1)^{n-|S|}\)

至于这个方案数怎么计算,交给矩阵树定理就好啦

复杂度\(O(2^nn^3)\)

代码

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=18;
const int mod=1e9+7;
inline int read() {
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n,cnt[131078],num[maxn],N;
int a[maxn][maxn];
std::vector<int> x[maxn],y[maxn];
inline int ksm(int a,int b) {
	int S=1;
	while(b) {if(b&1) S=(1ll*S*a)%mod;b>>=1;a=(1ll*a*a)%mod;}
	return S;
}
inline int qm(int a) {return a>=mod?a-mod:a;}
inline int solve(int S) {
	for(re int i=1;i<n;i++)
		for(re int j=1;j<n;j++)
			a[i][j]=0;
	for(re int i=1;i<n;i++) {
		if(!(S&(1<<(i-1)))) continue;
		for(re int j=1;j<=num[i];j++) {
			int u=x[i][j-1],v=y[i][j-1];
			a[u][u]++,a[v][v]++;
			a[u][v]--,a[v][u]--;
		}
	}
	for(re int i=1;i<n;i++)
		for(re int j=1;j<n;j++) 
			if(a[i][j]<0) a[i][j]+=mod;
	int o=(cnt[S^N]&1),ans=1;
	for(re int i=1;i<n;i++) {
		int p;
		for(p=i;p<n;++p) if(a[p][i]) break;
		if(p==n) return 0;
		if(i!=p) std::swap(a[p],a[i]),o^=1;
		int Inv=ksm(a[i][i],mod-2);
		for(re int j=i+1;j<n;j++) {
			int t=(1ll*Inv*a[j][i])%mod;
			for(re int k=i;k<n;k++)
				a[j][k]=qm(a[j][k]-1ll*a[i][k]*t%mod+mod);
		}
	}
	for(re int i=1;i<n;i++)
		ans=(1ll*ans*a[i][i])%mod;
	if(o) return qm(mod-ans);
	return ans;
}
int main() {
	n=read();N=(1<<(n-1))-1;
	for(re int i=1;i<=N;i++) cnt[i]=cnt[i>>1]+(i&1);
	for(re int i=1;i<n;i++) {
		num[i]=read();
		for(re int j=1;j<=num[i];j++)
			x[i].push_back(read()),y[i].push_back(read());
	}
	int tot=0;
	for(re int i=0;i<=N;i++)
		tot=qm(tot+solve(i));
	printf("%d\n",tot);
	return 0;
}
posted @ 2019-04-17 14:05  asuldb  阅读(259)  评论(0)    收藏  举报