BZOJ3659 Which Dreamed It

Which Dreamed It

有n个房间,每个房间有若干把钥匙能够打开特定房间的门。

你会做这么件事情:最初你在房间1。每当你到达一个房间,你可以选择该房间的一把钥匙,前往该钥匙对应的房间,并将该钥匙丢到垃圾桶中。

你希望:最终回到房间1,且垃圾桶中有所有的钥匙。

求方案数。两组方案不同,当且仅当使用钥匙的顺序不同。注意,每把钥匙都是不同的。

房间数小于等于100,钥匙数小于等于200000。

题解

题目要求的就是有向图欧拉回路个数。

矩阵树定理:
https://www.cnblogs.com/zj75211/p/8039443.html

BEST定理:
https://www.cnblogs.com/Paul-Guderian/p/10294032.html
https://en.wikipedia.org/wiki/BEST_theorem

It is a property of Eulerian graphs that tv(G) = tw(G) for every two vertices v and w in a connected Eulerian graph G.

注意求外向树个数的时候不考虑自环。

因为欧拉回路本质上是一个环,所以同一个环会被起点算上起点度数degs次。所以实际上连乘里面的\((deg_s-1)!=\frac{deg_s!}{deg_s}\)。如果像这道题一样认为欧拉回路是一个序列的话,最后还要再把degs乘回来。

CO int N=100+10,M=200000+10;
int fac[M];
int deg[N],a[N][N];

void real_main(int n){
	memset(a,0,sizeof a);
	for(int u=1;u<=n;++u){
		int m=read<int>();
		deg[u]=m;
		while(m--){
			int v=read<int>();
			if(v==u) continue;
			++a[v][v],a[u][v]=add(a[u][v],mod-1);
		}
	}
	if(n==1){
		printf("%d\n",fac[deg[1]]);
		return;
	}
	int ans=1;
	for(int i=1;i<n;++i){
		int p=i;
		for(int j=i;j<n;++j)
			if(a[j][i]) {p=j;break;}
		if(p!=i) swap(a[p],a[i]),ans=mod-ans;
		ans=mul(ans,a[i][i]);
		if(!ans) break;
		int inv=fpow(a[i][i],mod-2);
		for(int j=i+1;j<n;++j){
			int coef=mul(mod-a[j][i],inv);
			for(int k=i;k<n;++k) a[j][k]=add(a[j][k],mul(coef,a[i][k]));
		}
	}
	if(!ans){
		puts("0");
		return;
	}
	for(int i=1;i<=n;++i) ans=mul(ans,fac[deg[i]-1]);
	ans=mul(ans,deg[1]);
	printf("%d\n",ans);
}
int main(){
	fac[0]=1;
	for(int i=1;i<M;++i) fac[i]=mul(fac[i-1],i);
	for(int n;read(n);) real_main(n);
	return 0;
}

posted on 2019-12-10 21:27  autoint  阅读(325)  评论(0编辑  收藏  举报

导航