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;
}
静渊以有谋,疏通而知事。