http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=34531
题意:
给出n个人,以及一些父子关系,要求对这n个人构成一个排列,其中父亲必须排在儿子的前面。问一共有多少种排列方式。
部分没有父亲的人,表示他为祖先(根节点)
对于每一个祖先节点,我们把以他为根的整棵树 内部 按照要求的 合法排序方案为S【1】,于是我们得到S【2】、S【3】.....S【k】
对每一树上,总的节点数 记为 num[1]、num[2].....num[k]
对于每一棵树,他们之间的节点是没有影响的,是可以互相插入的.
最终 求的是一个 长度为n的序列
ans=1;
对第一棵树, ans=ans* C(N,num[1])*S【1】;//表示n个位置选num[1]个位置,放这棵树的s[1]种方案;
同理,对第二棵树, ans=ans* C(N-num[1],num[2])*S【2】;//表示剩下的N-num[1]个位置选num[2]个位置,放这棵树的s[2]种方案;
.......
那么只需要求的 是 num[]、S[]、以及一个组合数
对于num,用dfs对根节点搜一遍就好了(没必要每个点都搜一遍,会TLE,只对根节点搜就好了)
对S【】,也是dfs, 计算子树的内部排序方案数的方法 与 最后计算 每一棵子树之间的 答案【红色字体部分】是一样的,
对于组合数,因为模是一个素数,直接用逆元计算组合计算中的阶乘相除部分就好了。。。
ac代码:
<pre name="code" class="cpp">#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <iostream> #include <queue> #include <map> #include <set> #include <vector> using namespace std; const long long mod =1000000007; long long fast_m(long long a,long long b) { long long x,ans=1; x=a; while(b) { if (b&1) ans=(ans*x)%mod; x=(x*x)%mod; b/=2; } return ans; } long long fac[40005]; //阶乘预处理 long long cal(long long x,long long y) //计算组合数 { long long ans=1; ans*=(fac[x]*fast_m(fac[y]*fac[x-y]%mod,mod-2))%mod; //(a/b)%mod,等价于(a*b^(mod-2))%mod mod必须为素数 return ans%mod; } vector<long long> tm[40005]; //存的是以i的儿子序号 long long total[40005]; // 记录以i为根的子树上的所有节点数 int vis[40005]; //vis[i]=1表示这个节点不会是祖先【做过儿子了】 long long an[40005]; // 记录以i为根的子树,其内部所有节点的合法排列方案数 long long dfs(long long x); //求an数组函数 long long cal_son(long long x); //求total数组的函数 int main() { long long i; fac[0]=1; for( i=1;i<=40000;i++) fac[i]=fac[i-1]*i%mod; int t; cin>>t; while(t--) { memset(an,0,sizeof(an)); memset(vis,0,sizeof(vis)); long long n,m; scanf("%lld%lld",&n,&m); for (i=1;i<=n;i++) tm[i].clear(); long long tmp,fa; for (i=1;i<=m;i++) { scanf("%lld%lld",&tmp,&fa); tm[fa].push_back(tmp); vis[tmp]=1;//标记 tmp做过儿子 } for (i=1;i<=n;i++) { if (vis[i]) continue; if (tm[i].size()) { cal_son(i) ; //计算所有 子树的 【所有节点数】 } else total[i]=1; //单独一个点 } for (i=1;i<=n;i++) { if (vis[i]) continue; //非树根 if (tm[i].size()) dfs(i); //求该棵数的内部排序方案数 } long long ans=1; long long tmp_n=n; for (i=1;i<=n;i++) { if (vis[i]) continue;//做过儿子的 ans=ans*cal(tmp_n,total[i])%mod; tmp_n-=total[i]; //愚蠢的人类啊..之前直接用n减,结果造成RE找了半天 if (an[i]) //方案不存在表示为单独点,也就是唯一的放法 ans=ans*an[i]%mod; } printf("%lld\n",ans%mod); } return 0; } long long dfs(long long x) { long long ans=1,i; for (i=0;i<tm[x].size();i++) { long long tt=tm[x][i]; if (tm[tt].size()) { dfs(tt); //RE了半天,写成dfs(i)了原来 } } long long sum=total[x]-1; for (i=0;i<tm[x].size();i++) { long long num=tm[x][i]; long long tt=total[num]; ans=ans*cal(sum,tt)%mod; if (an[num]) ans=ans*an[num]%mod; sum-=tt; } return an[x]=ans%mod; } long long cal_son(long long x) { int son=0; son+=tm[x].size(); int i; for (i=0;i<tm[x].size();i++) { long long tt=tm[x][i]; if (tm[tt].size()) { son+=cal_son(tt); //RE了半天,写成dfs(i)了原来 } else { total[tt]=1; } } total[x]=son+1; //加上自己 return son; return 0; }