【组合计数】树上拓扑序 UVA11174 Stand in a Line
板子题:
https://www.luogu.com.cn/problem/UVA11174
分析
整个图形成了一片森林,我们记一棵树(根节点记为 \(root\))的排序方案数为 \(f[root]\)。
我们记 \(root\) 以下的子节点(即它们的父节点为 \(root\))为 \(c_1,c_2...c_k\)。
方便起见,记 \(sz[u]\) 为以其为根的子树大小。
我们看看如何选取可以构成一个合法的序列:
首先序列第一个元素必然是根 \(root\)。
对子树内部的点进行排序,由乘法原理知一定有 \(\prod_{i=1}^k f[c_i]\) 种方案。
假设每棵子树内部的点已经排好一定的顺序,那么每个子树内的点都可以看成是相同的元素,因此排列计数的方案数为 \(\frac{(sz[root]-1)!}{\prod_{i=1}^k sz[c_i]!}\)。
因此由乘法原理,总共的方案数为:
\[f[root] = \prod_{i=1}^k f[c_i]\times \frac{(sz[root]-1)!}{\prod_{i=1}^k sz[c_i]!}
\]
假设 \(c_i\) 有 \(k_i'\) 个子节点(记为 \(c'_j\)),类似地有:
\[f[c_i] = \prod_{j=1}^{k_i'} f[c'_j]\times \frac{(sz[c_i]-1)!}{\prod_{i=1}^{k_i'} sz[c'_j]!}
\]
对所有节点求积,即得
\[f[root] =\frac{(sz[root]-1)!}{\prod sz[u]}
\]
其中 \(u\) 代表除了根以外的所有节点。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
using ll=long long;
const int N=4e4+5, M=N<<1, mod=1e9+7;
int n, m;
struct node{
int to, next;
}e[M];
int h[N], tot;
void add(int u, int v){
e[tot].to=v, e[tot].next=h[u], h[u]=tot++;
}
bool vis[N];
int sz[N];
void dfs(int u, int fa){
sz[u]=1;
for(int i=h[u]; ~i; i=e[i].next){
int go=e[i].to;
if(go==fa) continue;
dfs(go, u);
sz[u]+=sz[go];
}
}
ll fpow(ll x,ll p)
{
ll res=1;
for(;p;p>>=1,x=x*x%mod)
if(p&1)res=res*x%mod;
return res%mod;
}
ll inv(ll x){
return fpow(x,mod-2)%mod;
}
ll fac[N];
void init(){
fac[0]=1;
for(int i=1; i<N; i++) fac[i]=fac[i-1]*i%mod;
}
ll C(ll a, ll b){
return fac[a]*inv(fac[b])%mod*inv(fac[a-b])%mod;
}
signed main(){
init();
int T; cin>>T;
while(T--){
memset(h, -1, sizeof h), tot=0;
memset(vis, 0, sizeof vis);
cin>>n>>m;
for(int i=0; i<m; i++){
int u, fa; cin>>u>>fa;
add(fa, u), add(u, fa);
vis[u]=true;
}
for(int i=1; i<=n; i++) if(!vis[i]) add(i, 0), add(0, i);
dfs(0, -1);
int t=1;
for(int i=1; i<=n; i++) t=t*inv(sz[i])%mod;
cout<<fac[sz[0]-1]*t%mod<<endl;
}
return 0;
}