洛谷5807-BEST定理(欧拉回路计数)
link:https://www.luogu.com.cn/problem/P5807
有 \(n\) 个房间,每个房间有若干把钥匙能够打开特定房间的门。最初在房间 \(1\)。每到达一个房间,你可以选择该房间的一把钥匙,前往该钥匙对应的房间,并将该钥匙丢到垃圾桶中。
你希望最终回到房间 \(1\),且垃圾桶中有所有的钥匙。求方案数模 \(10^6+3\)(是个质数)。
注:每把钥匙都是不同的。
\(1\leq n\leq 100\),钥匙数 \(\leq 2\times 10^5\).
每把钥匙看成一条边,房间看成点,就是问有多少从 \(1\) 开始的欧拉回路,根据BEST定理,答案是:
\[ans=t^{root}\times deg^{out}(1)\times \prod_{i=1}^n (deg^{out}(i)-1)!
\]
其中 \(t^{root}\) 表示内向生成树的个数,可以这样想:考虑一条欧拉回路,把 \(1\) 当做“起点”(实际上回路并没有起点),把每个点(除了1号)的最后一条出边拿出来,会得到一个内向生成树,而每个点剩下的 \(deg^{out}(i)-1\) 条边任意排列,都会得到一种不同的回路。
对于指定起点,再乘上起点的度数,表示要先跑哪条边。
具体实现起来:
- 首先BEST定理的前提是存在欧拉回路,需要进行一个简单的判断,对于单点是不用管的,直接去掉(具体实现的时候并不需要重新标号,只需要在算行列式的时候判掉)
- 欧拉回路的充要条件就直接判断每个点出入度,并且除了单点以外的点都要和1弱连通(是的,只要弱连通,一个并查集足够)
- 只要算行列式,消元消成上三角即可
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
typedef long long ll;
const int MOD=1e6+3;
const int N=110;
const int M=2e5+10;
int ksm(int a,int b){
int ret=1;
for(;b;b>>=1,a=(ll)a*a%MOD)if(b&1)ret=(ll)ret*a%MOD;
return ret;
}
int inv(int x){return ksm(x,MOD-2);}
int n,fact[M],fa[N];
int deg_in[N],deg_out[N];
vector<vector<int>> G,L;
int guass(vector<vector<int>> a,int n){
int det=1;
rep(i,1,n){
int k=i;
rep(j,i,n)if(abs(a[j][i])>abs(a[k][i]))k=j;
// if(a[k][i]==0&°_out[i])return 0;
swap(a[i],a[k]);
if(i!=k)det=(MOD-det);
rep(j,i+1,n){
int r=(ll)a[j][i]*inv(a[i][i])%MOD;
rep(k,1,n)a[j][k]=(a[j][k]-(ll)a[i][k]*r%MOD+MOD)%MOD;
}
}
rep(i,1,n)if(deg_out[i])det=(ll)det*a[i][i]%MOD;
return det;
}
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void addEdge(int u,int v){
deg_in[v]++;deg_out[u]++;
L[u][u]++;
L[u][v]=(MOD+L[u][v]-1)%MOD;
fa[u]=find(v);
}
int work(){
//init
cin>>n;
G=vector<vector<int>>(n+1);
L=vector<vector<int>>(n+1,vector<int>(n+1));
rep(i,1,n)fa[i]=i,deg_in[i]=deg_out[i]=0;
//read edge
rep(i,1,n){
int sz,x;cin>>sz;
rep(j,1,sz){
cin>>x;
addEdge(i,x);
}
}
//check euler loop
rep(i,1,n)if(deg_in[i]!=deg_out[i]||(deg_in[i]&&find(i)!=find(1)))return 0;
//BEST theorem
int sz=0;
rep(i,1,n)if(find(i)==find(1))sz++;
if(sz==1)return 1;
int ans=guass(L,n-1);
rep(i,1,n)if(deg_out[i])ans=(ll)ans*fact[deg_out[i]-1]%MOD;
return (ll)ans*deg_out[1]%MOD;
}
int main(){
fact[0]=1;
rep(i,1,M-5)fact[i]=(ll)fact[i-1]*i%MOD;
int T;cin>>T;
rep(tc,1,T)cout<<work()<<endl;
return 0;
}
BEST其实是4个人名的缩写:N. G. de Bruijn, Tatyana Ehrenfest, Cedric Smith and W. T. Tutte.
参考博客:BEST定理