题目大意:有一个村庄,里面有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;
}
posted on 2018-01-03 16:49  nhc2014  阅读(272)  评论(0编辑  收藏  举报