UVa 11174 Stand in a Line(递推+排列组合+逆元)
思路:
1.我们首先根据父子关系可以建立一棵树(或者森林),我们设立一个虚根连接森林中所有树的根结点,那么一颗树就建好了,我们标记虚根为序号0
;
2.我们设表示以为根节点的树,这些人的排列总数,我们设表示以为根结点的树的结点数量,根据排列组合的知识我们可以得到
3.这一步需要一些抽象想象能力,我们从根结点开始用公式往下计算,一直算到叶子结点,可以发现所有非根结点的结点i
,都有一次(s(i)-1)!
在分子上,都有一次s(i)!
在分母上,于是就可以上下抵消只剩下一个s(i)
在分母上,最后我们可以算得
至于计算s(i)
,一次dfs
就可以解决了;
4.既然涉及到除法取模问题,那么就需要求逆元计算了,方法有扩展欧几里得和费马小定理,不会的朋友可以自行学习,这里就不赘述了~
代码:
#include<bits/stdc++.h>
using namespace std;
#define pt(a) cerr<<a<<"---\n"
typedef long long LL;
const int maxn=40000+5;
const LL mod=1e9+7;
int m;
vector<int> v[maxn];
LL n,s[maxn],tag[maxn];
void extgcd(int a,int b,int& x,int& y){
if(b==0){x=1,y=0;return;}
extgcd(b,a%b,x,y);
int tmp=x;x=y;y=tmp-(a/b)*y;
}
int mod_inv(int a,int m){
int x,y;
extgcd(a,m,x,y);
return (m+x%m)%m;
}
void dfs(int root){
s[root]=1;
if(v[root].size()) for(auto e:v[root]) dfs(e),s[root]+=s[e];
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) tag[i]=1;
for(int i=0;i<m;i++){
int a,b; cin>>a>>b;
v[b].push_back(a);
tag[a]=0;
}
for(int i=1;i<=n;i++) if(tag[i]) dfs(i);
LL res=1ll;
for(LL i=2;i<=n;i++) res*=i,res%=mod;
// for(int i=1;i<=n;i++) pt(s[i]);
for(int i=1;i<=n;i++) res*=mod_inv(s[i],mod),res%=mod;
cout<<res<<'\n';
}
void clear(){
for(int i=0;i<=n;i++){
v[i].clear();
s[i]=tag[i]=0;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
// freopen("Arutoria.txt","r",stdin);
int t; cin>>t;
while(t--) solve(),clear();
return 0;
}