保镖排队

保镖排队 (Standard IO)
Time Limits: 1000 ms  Memory Limits: 65536 KB     
Goto ProblemSet
Description

【问题背景】
  教主LHX作为知名人物,时刻会有恐怖分子威胁他的生命。于是教主雇佣了一些保镖来保障他的人生安全。
【题目描述】
  教主一共雇佣了N个保镖,编号为1~N。每个保镖虽然身手敏捷武功高强,但是他在其余N-1个保镖里,都会有一个“上司”,他会对他的上司言听计从。但一号保镖例外,他武功盖世,不惧怕其余任何保镖,所以他没有上司。
  教主LHX会对这N个保镖进行定期视察。每次视察的时候,首先会让所有保镖排队。
  对于每个保镖,在他心目中会对他的所有下属的武功实力排个队。
  现在教主要求排出来的队伍满足:①互为上司-下属的两个保镖,上司在前,下属在后 ②对于一个保镖的所有下属,武功实力较强的在前,较弱的在后。
  教主想知道,总的排队方法数除以10007的余数是多少。

Input

 输入的第一行为一个正整数T,表示了数据组数。
  对于每组数据:
  第一行为一个正整数N。
  接下来N行,每行描述一个保镖。
  第i+1行,会有一个整数K,代表第i个保镖的下属个数,接下来K个数,代表第i个保镖的下属按照武功实力从高到低的编号。
Output

  输出包括C行,每行对于每组数据输出方案数mod 10007后的结果。
Sample Input
2
5
2 2 3
2 4 5
0
0
0
7
2 2 3
2 4 5
2 6 7
0
0
0
0

Sample Output
3
10
【样例说明】
  对于第1组数据,有以下3种排列是合法的:
  1 2 4 3 5
  1 2 3 4 5
  1 2 4 5 3
  同时满足了1在2与3之前且2在3之前,2在4与5之前且4在5之前

【数据规模】
  对于20%的数据,有N ≤ 9;
  对于40%的数据,有对于所有K,有K ≤ 2;
  对于60%的数据,有N ≤ 100;

  对于100%的数据,有T ≤ 10,N ≤ 1000,K ≤ N。


这题挺有收获,在考试时,总是想在原树上求dp方程,求不出。后面听了正解,原来是先多叉转二叉再递推上去。

为什么多叉转二叉呢,因为它的序列是个拓扑序列,每个点要在它左兄弟后面,最左子节点要在其父节点后,于是每个点入度仅为1;出度为1或2(又有右兄弟又有儿子)

那么递推方程就不难推了如果一个点出度为1,F[p]=F[next]

否则                                 

F[p]=F[next1]*F[next2]*Combination(s1,s1+s2)

就是子数方案数的积乘上组合数(s指子树节点数)

#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       
using namespace std;
int f[1002],s[1001],ans,i,j,k,T,d[1001][2],n;
bool v[1001];
int fac[10010];

void factorizate(int a,int b){
	int k=a,i=2;
	while(i<=floor(sqrt(k)))
		if(!(k%i))k/=i,fac[i]+=b;else i++;
	fac[k]+=b;
}

int combine(int r,int n){
	memset(fac,0,sizeof(fac));
	for(int i=n-r+1;i<=n;i++)factorizate(i,1);
	for(int i=2;i<=r;i++)factorizate(i,-1);
	int g(1);
	for(int i=2;i<=n;i++)
		for(int j=1;j<=fac[i];j++)g=(g*i)%10007;
	return g;
}

void dfs(int p){
	if(d[p][1]==0&&d[p][0]==0){
		f[p]=1;
		s[p]=1;
		return;
	}
	if((!d[p][1])&&(d[p][0])){
		dfs(d[p][0]);
		f[p]=f[d[p][0]];
		s[p]=s[d[p][0]]+1;
		return;
	}
	if((!d[p][0])&&(d[p][1])){
		dfs(d[p][1]);
		f[p]=f[d[p][1]];
		s[p]=s[d[p][1]]+1;
		return;
	}
	if(d[p][0]&&d[p][1]){
		dfs(d[p][0]);dfs(d[p][1]);
		f[p]=(((f[d[p][0]]*f[d[p][1]])%10007)*combine(s[d[p][0]],s[d[p][0]]+s[d[p][1]]))%10007;
		s[p]=s[d[p][0]]+s[d[p][1]]+1;
		return ;
	}
	
}

int main(){
	freopen("c.out","w",stdout);
	freopen("c.in","r",stdin);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		memset(d,0,sizeof(d));
		for(i=1;i<=n;i++){
			scanf("%d",&k);	
			int la;
			for(j=1;j<=k;j++){
				int u;
				scanf("%d",&u);
				if(j==1)d[i][1]=u;else d[la][0]=u;
				la=u;
			}
		}
		memset(v,0,sizeof(v));
		ans=0;
		v[1]=1;
		memset(s,0,sizeof(s));
		memset(f,0,sizeof(f));
		dfs(1);
		printf("%d\n",f[1]);
	}
}

      
      
     
     
    
    
   
   
要习惯性用图论表示实质关系,原图是上下属关系,而二叉图则是排列的关系图,我们需要的是排列关系图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值