洛谷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&&deg_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定理

posted @ 2024-02-26 23:41  yoshinow2001  阅读(156)  评论(0编辑  收藏  举报