题目大意:有一个村庄,里面有n个人,有些人的父亲也在这个村庄里。我们现在要将这些人排成一个队列,一个人不能排在他的父亲前面,问这种队列有多少种可能?
解:很明显我们可以先建树。
然后假设现在有这样一棵树:
我们要如何统计父节点的方案数呢?
首先我们先要确定每个子节点中的排列顺序,总共有6*8*2种,然后在其中一种的情况下,考虑怎么把它们穿插起来。
如果我们把同一棵子树中的元素都看成相同元素,那么问题就变成了,现在有三种数(在样例中),它们的个数不同,问由它们组成的不同的排列有多少种?(全部的数都要用上)
在这个样例中,每种数的个数分别是2,3,5.我们答案为x,那么可以得到一个等式2!*3!*5!*x=(2+3+5)!,这个可以自己想一下。所以我们就可以的得到答案。
设当前子树的排法有f[i]种,子节点有s[i]个,则通过以上分析我们可得:
f[i]=f[son1]*f[son2]*...*f[sonk]*(s[i]-1)/s[son1]!*s[son2]!*...*s[sonk]!(son1,son2...sonk是i的各个子节点)
其实这样已经可以做了,不过我们还可以化简
你会发现在最终的答案中,s[i]!会在分子中出现一次,(s[i]-1)!会在分母中出现一次(叶子结点不影响),所以把他们全部都约掉最终答案就成了:
ans=(s[root]-1)!/s[1]!*s[2]!*s[3]!*...*s[n]!;
所以只需深搜一遍,求出每棵子树的大小,然后预处理阶乘,并且因为模数是一个质数,所以根据小费马定理,用快速幂求出一个数的^(mo-2),即为这个数的逆。
程序:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #define maxn 40009 #define mo 1000000007 using namespace std; typedef long long ll; int n,m,s[maxn],fa[maxn]; vector<int>g[maxn]; ll pow(ll now,int tot) { ll sum=1; while (tot) { if (tot&1) sum=(sum*now)%mo; now=(now*now)%mo; tot=tot>>1; } return sum; } void dfs(int x) { s[x]=1; for (int i=0;i<g[x].size();i++) dfs(g[x][i]),s[x]+=s[g[x][i]]; } int main() { int t; scanf("%d",&t); while (t--) { scanf("%d%d",&n,&m); for (int i=0;i<=n;i++) { while (g[i].size()) g[i].pop_back(); fa[i]=0; } int x,y; for (int i=1;i<=m;i++) { scanf("%d%d",&x,&y); fa[x]=y; } for (int i=1;i<=n;i++) g[fa[i]].push_back(i); dfs(0); ll ans=1; for (int i=2;i<=n;i++) ans=(ans*i)%mo; for (int i=1;i<=n;i++) ans=(ans*pow(s[i],mo-2))%mo; printf("%lld\n",ans); } return 0; }