点此看题
这篇博客主要记录我的感性理解,相信能帮助你直观地理解 \(\tt BEST\) 定理。
首先对于一条欧拉路径,我们考虑保留每个点的最后一条出边。可以证明出边一定构成一棵内向树,我们只需要证明不会构成环,而如果构成环,考虑走完环的最后一条出边一定会停留在这个点,那么就无法停留在出发点了,这与欧拉路径是矛盾的。
所以我们考虑统计出内向树的个数,那么剩下的边怎么排列呢?发现任意排列都可以构成欧拉路径,所以要乘上 \(\prod_{i=1}^n(deg(i)-1)!\),那么我们就得到了 \(\tt BEST\) 定理,设 \(T\) 为内向生成树个数:
\[T\cdot \prod_{i=1}^n(deg(i)-1)! \]本题起点第一条边的选择也是需要考虑的,所以还要在此基础上乘上 \(deg(1)\)
使用此定理的条件是存在欧拉路径,我们需要判断每个点入度\(=\)出度,以及所有的边弱联通。计算行列式的时候对于没有边的点需要忽略(要不然就把 \(0\) 乘进去了)
#include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> using namespace std; const int N = 105; const int M = 200005; const int MOD = 1e6+3; #define int long long int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int T,n,fac[M],in[N],out[N],fa[N],a[N][N]; int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } int qkpow(int a,int b) { int r=1; while(b>0) { if(b&1) r=r*a%MOD; a=a*a%MOD; b>>=1; } return r; } int gauss() { int ans=1; for(int i=2;i<=n;i++) { for(int j=i;j<=n;j++) if(!a[i][i] && a[j][i]) { swap(a[i],a[j]); ans=MOD-ans; break; } if(out[i]) ans=ans*a[i][i]%MOD; for(int j=1;j<=n;j++) { if(i==j || !a[j][i]) continue; int t=a[j][i]*qkpow(a[i][i],MOD-2)%MOD; for(int k=1;k<=n;k++) a[j][k]=(a[j][k]-t*a[i][k]%MOD+MOD)%MOD; } } return ans; } void work() { n=read();int fl=1; memset(a,0,sizeof a); for(int i=1;i<=n;i++) fa[i]=i,in[i]=out[i]=0; for(int i=1;i<=n;i++) { int k=read(); while(k--) { int j=read();out[i]++;in[j]++; a[j][j]=(a[j][j]+1)%MOD; a[i][j]=(a[i][j]+MOD-1)%MOD; fa[find(i)]=find(j); } } for(int i=1;i<=n;i++) if(in[i]!=out[i] || (out[i] && find(i)^find(1))) {puts("0");return ;} for(int i=1;i<=n;i++) fl&=(out[i]==0); if(fl) {puts("1");return ;} int ans=gauss()*out[1]%MOD; for(int i=1;i<=n;i++) if(out[i]) ans=ans*fac[out[i]-1]%MOD; printf("%lld\n",ans); } signed main() { T=read();fac[0]=1; for(int i=1;i<=200000;i++) fac[i]=fac[i-1]*i%MOD; while(T--) work(); }