• [树形dp][组合数] JZOJ P1794 保镖排队


    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

    Hint

    【样例说明】
      对于第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。

    题解

    • 首先要明确的是,属于不同子树的点可以混在一起,只要同一子树的有序就可以了。

    • 如果一个结点有两个儿子l,r,并且l排在r的前面,那么总的方法数就是f[l]*f[r]*C(size[l]+size[r]-1,size[l]-1)

    • 其含义是:容易想到,根据乘法原理,总共会有f[l]*f[r]种情况,对于每种情况,都可以有C(size[l]+size[r]-1,size[l]-1)种合并方法

    • 子树总共有size[l]+size[r]个结点(不算根结点),因为l必排最前面,所以剩下的位置就是size[l]+size[r]-1个,其中属于l的总共就有size[l]-1个,这个就直接用组合数就算出来了

    • 然后再一个个往后合并就好了
    • 组合数可以直接用杨辉三角推出来,如果现算的话时间复杂度就比较高。尤其是RQNOJ的测评机都很蜗牛。

    代码

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<algorithm>
     4 using namespace std;
     5 int n,c[2500][2500],map[2500][2500],s[2500],f[2500],t;  
     6 void dp(int x)
     7 {
     8     int t; 
     9     f[x]=1; s[x]=0;
    10     for (int i=map[x][0];i>=1;i--)
    11     {
    12            t=map[x][i];
    13            dp(t);
    14            f[x]=(((f[x]*f[t])%10007)*(c[s[t]+s[x]-1][s[t]-1]))%10007;
    15            s[x]+=s[t];
    16     }
    17     s[x]=s[x]+1; 
    18 }
    19 int main()
    20 {
    21     c[0][0]=1;
    22     for (int i=1;i<2500;++i)
    23     {
    24            c[i][0]=c[i][i]=1; 
    25            for(int j=1;j<i;++j) c[i][j]=(c[i-1][j-1]+c[i-1][j])%10007;
    26     }
    27     scanf("%d",&t);
    28     for (int i=1;i<=t;i++)
    29     {
    30            scanf("%d",&n);
    31         for (int i=1;i<=n;++i)
    32         {
    33                scanf("%d",&map[i][0]);
    34                for(int j=1;j<=map[i][0];j++) scanf("%d",&map[i][j]);
    35         }  
    36            dp(1); 
    37            printf("%d
    ",f[1]);
    38     }
    39     return 0;
    40 }
  • 相关阅读:
    一个购物网站的思路设计分享
    B/S和C/S的区别(转)
    TreeSet
    计算出给你一个随机乱敲的一个字符串最多的一个
    JavaScript来实现打开链接页面(转载)
    js小数计算小数点后显示多位小数(转)
    java中使用 正则 抓取邮箱
    浅谈 正则表达式
    jQuery中each()、find()、filter()等节点操作方法
    Xcode插件VVDocumenter Alcatraz KSImageNamed等安装
  • 原文地址:https://www.cnblogs.com/Comfortable/p/8427405.html
Copyright © 2020-2023  润新知