您的位置:首页 > 其它

UVA 11174-组合数学+组合数取模+dfs

2015-10-29 07:38 274 查看
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;
}

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: