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

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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值