【树形DP】保镖排队

保镖排队

(p3.pas/cpp/in/out)

 

【问题背景】

  教主LHX作为知名人物,时刻会有恐怖分子威胁他的生命。于是教主雇佣了一些保镖来保障他的人生安全。

 

【题目描述】

  教主一共雇佣了N个保镖,编号为1~N。每个保镖虽然身手敏捷武功高强,但是他在其余N-1个保镖里,都会有一个“上司”,他会对他的上司言听计从。但一号保镖例外,他武功盖世,不惧怕其余任何保镖,所以他没有上司。

  教主LHX会对这N个保镖进行定期视察。每次视察的时候,首先会让所有保镖排队。

对于每个保镖,在他心目中会对他的所有下属的武功实力排个队。

  现在教主要求排出来的队伍满足:①互为上司-下属的两个保镖,上司在前,下属在后②对于一个保镖的所有下属,武功实力较强的在前,较弱的在后。

  教主想知道,总的排队方法数除以10007的余数是多少。

 

【输入格式】

  输入的第一行为一个正整数T,表示了数据组数。

对于每组数据:

第一行为一个正整数N

  接下来N行,每行描述一个保镖。

  第i+1行,会有一个整数K,代表第i个保镖的下属个数,接下来K个数,代表第i个保镖的下属按照武功实力从高到低的编号。

 

【输出格式】

  输出包括C行,每行对于每组数据输出方案数mod 10007后的结果。

 

【样例输入】

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

 

【样例输出】

3

10

 

【样例说明】

对于第1组数据,有以下3种排列是合法的:

1 2 4 3 5

1 2 3 4 5

1 2 4 5 3

同时满足了123之前且23之前,245之前且45之前

 

【数据规模】

对于20%的数据,有9

对于40%的数据,有对于所有K,有2

对于60%的数据,有100

对于100%的数据,有101000N

 

【考察点】

树形DP

【思路】

看题目,给了若干的从属关系……所以很明显跟树有关了。

先把题目中的森林按照“左儿子,右兄弟”的原则(或者其他的什么原则)转化成一棵二叉树。我们用F[i]表示以i为根的子树的排列方案树,S[i]表示这棵子树的规模,经过一系列各种蛋疼的推导,我们可以找到转移方程: f[x]=f[L[x]]*f[R[x]]*C(s[L[x]]+s[R[x]]-1,s[L[x]]-1),其中C就是组合数呐、

然后就是万年不变的树形DP模式了……

 

话说……我做题的时候完全没有意识到那货是个组合数……我是观察+乱搞得出的……

【提交情况】

1AC

【经验】

那什么……多叉转二叉其实不一定要搞出一棵二叉树,就像线段树一样,心中有树就行了……

【收获】

我觉得搞完这道题……我的代码调试能力又有提升了……

【吐槽】

我怀疑我把Lazarus编译器调蹦了……上述转移方程的某一步计算总是跟手动不一样。搞了很久怒换C++,翻译一次果然就行了……虽然各种后续问题出现,又调了半天= =

还有就是……转二叉还是用标准方法好……奇葩方法很容易在调试的时候把人搅晕……

 

ACCode:


#include <cstdio>

#include <algorithm>

#include <cstring>

//#include <iostream>

 

using namespace std;

 

 

int N;

int c[2500][2500];

int map[2500][2500];

int s[2500],f[2500];

 

void init()

{

 freopen("p3.in","r",stdin);

  freopen("p3.out","w",stdout);

  

  

 c[0][0]=1;

 for(int i=1;i<2500;++i)

  {

   c[i][0]=c[i][i]=1;

    

   for(int j=1;j<i;++j)

     c[i][j]=(c[i-1][j-1]+c[i-1][j])%10007;

  }

  //翻译过来的时候忘掉了……怪不得半天没反应- - 

    

   //for (int i=0;i<=20;i++)

   //{

    //for (int j=0;j<=20;j++)

   //cout<<c[i][j]<<" ";

   //cout<<endl;

   //}

 }

  

 

void readdata()

{

 scanf("%d",&N);

 for(int i=1;i<=N;++i)

  {

   scanf("%d",&map[i][0]);

   for(int j=1;j<=map[i][0];j++)

     scanf("%d",&map[i][j]);

 }    

}

 

void solve(int x)  //递归没什么要说的……

{

  intt;

  

 f[x]=1; 

 s[x]=0; 

  

 for(int i=map[x][0];i>=1;i--)

  {

   t=map[x][i];

   //cout<<t<<endl;

   solve(t);

   f[x]=(((f[x]*f[t])%10007)*(c[s[t]+s[x]-1][s[t]-1]))%10007;  //Mod10007这种问题嘛……还是每个小运算都Mod一下,安全起见;这货就是转移方程了……

   //cout<<"f["<<x<<"]"<<f[x]<<endl;

   s[x]+=s[t];

   //cout<<s[x]<<" "<<s[t]<<endl;

  }

  

 s[x]=s[x]+1;  //记住维护S数组

}

 

 

int main()

{

  intt;

  

 init();

  

 scanf("%d",&t);

  

  for(int i=1; i<=t; i++)

  {

   readdata();

   solve(1); //递归处理

   //for (int i=1; i<=N; i++) cout<<f[i]<<" ";

   //cout<<endl;

   printf("%d\n",f[1]);

  }

 

 return 0;

}




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值