zoukankan      html  css  js  c++  java
  • 【ACM/ICPC2013】树形动态规划专题

    前言:按照计划,昨天应该是完成树形DP7题和二分图、最大流基础专题,但是由于我智商实在拙计,一直在理解树形DP的思想,所以第二个专题只能顺延到今天了。但是昨天把树形DP弄了个5成懂我是很高兴的!下面我把这7题的解题思想和部分代码分享给大家。

    题目一:皇宫看守
    问题描述:
    太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。
    皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。
    可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

    编程任务:
    帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

    数据输入:
    输入文件中数据表示一棵树,描述如下:
    第1行 n,表示树中结点的数目。
    第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(0<i<=n),在该宫殿安置侍卫所需的经费k,该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,…,rm。
    对于一个n(0 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。

    数据输出:
    输出文件仅包含一个数,为所求的最少的经费。

    样例输入:
    6
    1 30 3 2 3 4
    2 16 2 5 6
    3 5 0
    4 4 0
    5 11 0
    6 5 0

    样例输出:
    25

    分析:本来这套题里还有一个题目叫“战略游戏”,抽象后的模型跟这题是一样的,遇到那题的时候我没太理解题解是怎么用树形DP做的,于是我就跳过了。后来遇到这题发现不研究的话不行。题目说的很清楚,用最少的点覆盖所有的点,(二分图里有一个模型叫用最少的点覆盖所有的边,以后再议)。如果是一个图的话,它是个NP完全问题,但题目给出的是个树,避免了后效性的问题,所以可以用动态规划来解决。
    给出如下定义

      F[i,0]表示i点不放,且以i为根节点的子树(包括i节点)全部被观察到;
      F[i,1]表示i点不放,且以i为根节点的子树(可以不包括i节点)全部被观察到;
      F[i,2]表示i点放,且以i为根节点的子树全部被观察到;

    转移如下
    1、由F[i,0]定义可知,设j为i的儿子节点,至少要有一个i的儿子节点是放置守卫的,其余的儿子节点可放可不放,但由于根节点i不放,所以其余的儿子节点如果不放的话,必须保证能被观察到,即F[j][0];所以我们需要枚举必须放置的儿子节点,下面的转移方程描述的很清楚:

      F[i,0] = min{Sigma(min(F[j][0],F[j,2]))+F[k,2]},其中k为枚举的必放的儿子节点,j为除了k之外的儿子节点

    2、由F[i,1]定义可知,i可以被观察到也可以不被观察到,但儿子节点必须都要被观察到,转移如下:

      F[i,1] = Sigma(min(F[j,0],F[j,2])) j是i的儿子节点

    3、由F[i,2]定义可知,i点放置了守卫,所以对于每个儿子节点都能被观察到,取F[j,0],F[j,1],F[j,2]最小值即可:

      F[i,2] = min(F[j,0],F[j,1],F[j,2]) j是i的儿子节点
      对于叶节点i,F[i,0] = F[i,2] = data[i],F[i,1] = 0;

    看了题解我是恍然大悟啊,智商不够,这个转移方程还是比较难想的。

    参考代码

     1 //
     2 //  皇宫看守.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <algorithm>
    11 #include <cstdio>
    12 #include <cstring>
    13 #include <string>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 1500+10;
    18  
    19 int f[maxn][3],data[maxn],n,son[maxn][maxn],len[maxn],du[maxn],x,root;
    20  
    21 void doit(int x)
    22 {
    23     if (len[x] == 0)
    24     {
    25         f[x][0] = f[x][2] = data[x];
    26         f[x][1] = 0;
    27         return;
    28     }
    29     for (int i = 1;i <= len[x];i++)
    30         doit(son[x][i]);
    31     f[x][0] = INT_MAX;
    32     for (int i = 1;i <= len[x];i++)
    33     {
    34         int tmp = 0;
    35         for (int j = 1;j <= len[x];j++)
    36             if (i!=j)
    37                 tmp += min(f[son[x][j]][0],f[son[x][j]][2]);
    38         f[x][0] = min(f[x][0],tmp+f[son[x][i]][2]);
    39     }
    40     f[x][1] = 0;
    41     for (int i = 1;i <= len[x];i++)
    42         f[x][1] += min(f[son[x][i]][0],f[son[x][i]][2]);
    43     f[x][2] = data[x];
    44     for (int i = 1;i <= len[x];i++)
    45         f[x][2] += min(f[son[x][i]][0],min(f[son[x][i]][1],f[son[x][i]][2]));
    46 }
    47  
    48 int main()
    49 {
    50     scanf("%d",&n);
    51     memset(data,0,sizeof(data));             //存每个点放置守卫的代价
    52     memset(f,0,sizeof(f));                   //dp数组,F[i,j]含义如上述分析
    53     memset(du,0,sizeof(du));                 //存储每个点的入度,用于找出根节点
    54     memset(son,0,sizeof(son));               //存储每个点的儿子节点
    55     memset(len,0,sizeof(len));               //存储每个点儿子节点个数
    56     for (int i = 1;i <= n;i++)
    57     {
    58         scanf("%d",&x);
    59         scanf("%d%d",&data[x],&len[x]);
    60         for (int j = 1;j <= len[x];j++)
    61         {
    62             scanf("%d",&son[x][j]);
    63             du[son[x][j]]++;
    64         }
    65     }
    66     for (int i = 1;i <= n;i++)
    67         if (du[i] == 0)
    68         {
    69             root =i;
    70             break;
    71         }
    72     doit(root);
    73     printf("%d
    ",min(f[root][0],f[root][2]));
    74 }
    View Code

    题目二:选课(Vijos1180)
    【问题描述】
    大学里实行学分。每门课程都有一定的学分,学生只要选修了这门课并考核通过就能获得相应的学分。学生最后的学分是他选修的各门课的学分的总和。
    每个学生都要选择规定数量的课程。其中有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如,《数据结构》必须在选修了《高级语言程序设计》之后才能选修。我们称《高级语言程序设计》是《数据结构》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。为便于表述每门课都有一个课号,课号依次为1,2,3,……。下面举例说明
    课号 先修课号 学分
    1        无           1
    2         1            1
    3         2            3
    4        无           3
    5         2            4
    上例中1是2的先修课,即如果要选修2,则1必定已被选过。同样,如果要选修3,那么1和2都一定已被选修过。
    学生不可能学完大学所开设的所有课程,因此必须在入学时选定自己要学的课程。每个学生可选课程的总数是给定的。现在请你找出一种选课方案,使得你能得到学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。
    【输入格式】
    输入文件的第一行包括两个正整数M、N(中间用一个空格隔开)其中M表示待选课程总数(1≤M≤1000),N表示学生可以选的课程总数(1≤N≤M)。
    以下M行每行代表一门课,课号依次为1,2……M。每行有两个数(用一个空格隔开),第一个数为这门课的先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。学分是不超过10的正整数。
    【输出格式】
    输出文件第一行只有一个数,即实际所选课程的学分总数。以下N行每行有一个数,表示学生所选课程的课号。
    【输入样例】choose.in
    7 4
    2 2
    0 1
    0 4
    2 1
    7 1
    7 6
    2 2
    【输出样例】choose.out
    13
    2
    6
    7
    3

    分析:这是一个树形背包,在《背包九讲》里面,这种题型被称为泛化物品。用于求解多叉树上的背包问题。
    徐持衡大牛在2009年国家队论文《浅谈几类背包题》一文中给出了一种O(n*C)的算法,我昨天痛苦的理解了一天(仍然是智商不够)估计自己才理解了五成。下面我就说说我的理解。
    F[i,m]表示以i为根的子树被分配到m的容量所能获得的最大得分。
    对于i的每一个儿子j,先将F[i,0~m-v[j]]全部赋值给F[j,0~m-v[j]],按我的理解,就是留出j的空间(因为一定要放),后剩下的m-v[j]个空间的最优值赋值给以j为根的子树去进行递归计算,递归计算完j后,F[i,m] = max(F[i,m],F[j,m-v[j]]+c[j]),v[j]<=m<=M,按我的理解,计算完j子树后,因为F[j,m]的最优值是包含了F[i,m]的最优值计算的,也就是包含了j之前的i的儿子计算出来的最优值,所以只需要在F[i,m]和F[j,m-v[j]]+c[j](放j结点)中取最大值代替F[i,m]即可。
    以上就是我昨天一整天思考的结果。或许我理解的不对,或许在大牛眼里这很easy,不过对我来说已经是个很大的进步啦~

    参考代码

     1 //
     2 //  选课.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <algorithm>
    11 #include <cstdio>
    12 #include <cstring>
    13 #include <string>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 1000+10;
    18  
    19 int x,n,m,data[maxn],g[maxn][maxn],f[maxn][maxn];
    20  
    21 void doit(int root,int res)
    22 {
    23     if (res<=0) return;
    24     for (int i = 1;i <= g[root][0];i++)
    25     {
    26         for (int j = 0;j <= res-1;j++) f[g[root][i]][j] = f[root][j]+data[g[root][i]];    //先把i子树已经算过的最优值赋值给j子树
    27         doit(g[root][i],res-1);                                                           //递归计算j子树
    28         for (int j = 1;j <= res;j++)
    29             f[root][j] = max(f[root][j],f[g[root][i]][j-1]);                              //用j子树最优值来更新i子树
    30     }
    31 }
    32  
    33 int main()
    34 {
    35     scanf("%d%d",&n,&m);
    36     for (int i = 1;i <= n;i++)
    37     {
    38         scanf("%d%d",&x,&data[i]);
    39         g[x][0]++;
    40         g[x][g[x][0]] = i;
    41     }
    42     memset(f,0,sizeof(f));
    43     doit(0,m);
    44     printf("%d
    ",f[0][m]);
    45 }
    View Code

    题目三:技能树
    【问题描述】
    玩过Diablo的人对技能树一定是很熟悉的。一颗技能树的每个结点都是一项技能,要学会这项技能则需要耗费一定的技能点数。只有学会了某一项技能以后,才能继续学习它的后继技能。每项技能又有着不同的级别,级别越高效果越好,而技能的升级也是需要 耗费技能点数的。
    有个玩家积攒了一定的技能点数,他想尽可能地利用这些技能点数来达到最好的效果。因此他给所有的级别都打上了分,他认为效果越好的分数也越高。现在他要你帮忙寻找一个分配技能点数的方案,使得分数总和最高。
    【输入格式】
    第一行是一个整数n(1<=n<=20),表示所有不同技能的总数。接下来依次给出n个不同技能的详细情况。每个技能描述包括5行,第一行是该技能的名称,第2行是该技能在技能树中父技能的名称,为空则表示该技能不需要任何的先修技能便能学习。第3行是一个整数L(1<=L<=20),表示这项技能所能拥有的最高级别。第4行共有L个整数,其中第I个整数表示从地I-1级升到第I级所需要的技能点数(0级表示没有学习过)。第5行包括L个整数,其中第I个整数表示从第I-1级升级到第I级的效果评分,分数不超过20。在技能描述之后,共有两行,第1行是一个整数P,表示目前所拥有的技能点数。接下来1行是N个整数,依次表示角色当前习得的技能级别,0表示还未学习。这里不会出现非法情况。
    【输出格式】
    S,表示最佳分配方案所得的分数总和。
    【输入样例】
    3
    Freezing Arrow
    Ice Arrow
    3
    3 3 3
    15 4 6
    Ice Arrow
    Cold Arrow
    2
    4 3
    10 17
    Cold Arrow

    3
    3 3 2
    15 5 2
    10
    0 0 1
    【输出样例】
    42

    分析:容许我先乐呵一下。因为这是我第一道独立做出来的多叉树形背包的题目!(智商低就是容易满足。。)
    这个题目根上一题的区别在于,每个结点的v[i]和c[i]是可以在一定范围内变化的,也就是说,要枚举不同的v[j],然后下放到j子树,然后加上不同的c[j]进行比较。
    每个结点有初始等级,等级为0的不能作为根节点,必须要升级,那么对于等级不为0的儿子j,首先将i所获得的所有容量m全部赋值给儿子j,也就是说先不升级。然后再跟等级为0的儿子结点一样,从初始等级+1到最高等级进行枚举,设两个变量p(累加存储所需技能点数)和q(累加存储所获得的分数),进行赋值、递归、更新的三步计算。
    然后我一开始WA了一次,为什么呢,我想了一下才明白,枚举每一个等级,将i结点的最优值赋值给儿子j时,必须是任何升级前的初始值,不然每次用上一级的最优值来计算,会出现重复累加的情况,最后答案会变大,所以枚举到儿子结点j时,先用一个tmp数组存下F[i,0~m]的初始值,就是前面几个儿子的最优值,然后对于不升级(初始等级不为0)或者每一次升级,直接将tmp赋值给F[j,0~m-p]即可。
    吼吼,再次庆祝一下第一次做出来的题目。话说现在写题解感觉理解又加深了一步。

    参考代码

     1 //
     2 //  技能树.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <algorithm>
    11 #include <cstdio>
    12 #include <cstring>
    13 #include <string>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 20+10;
    18  
    19 string na[maxn],st;
    20  
    21 int f[maxn][100+10],son[maxn][maxn],len[maxn],n,m,l1[maxn],data[maxn],l2[maxn][maxn],sc[maxn][maxn],y,top;
    22  
    23 int findst(const string st)
    24 {
    25     for (int i = 1;i <= top;i++)
    26         if (st == na[i])
    27             return i;
    28     top++;
    29     na[top] = st;
    30     return top;
    31 }
    32  
    33 void doit(int root,int res)
    34 {
    35     int tmp[100+10];
    36     if (res < 0) return;
    37     for (int i = 1;i <= son[root][0];i++)
    38     {
    39         for (int k = 0;k <= res;k++)
    40             tmp[k] = f[root][k];                                              //先存储初始最优值
    41         if (l1[son[root][i]] != 0)                                            //如果初始等级不为0,那先尝试不升级的最优
    42         {
    43             for (int k = 0;k <= res;k++)
    44                 f[son[root][i]][k] = f[root][k];
    45             doit(son[root][i],res);
    46             for (int k = 0;k <= res;k++)
    47                 f[root][k] = max(f[root][k],f[son[root][i]][k]);
    48         }
    49         int p = 0,q = 0;
    50         for (int j = l1[son[root][i]]+1;j <= len[son[root][i]];j++)           //从当前等级升一级到最高等级进行枚举
    51         {
    52             p += l2[son[root][i]][j];        //累加所需技能点
    53             q += sc[son[root][i]][j];        //累加所得分数
    54             for (int k = 0;k <= res-p;k++)
    55                 f[son[root][i]][k] = tmp[k];
    56             doit(son[root][i],res-p);
    57             for (int k = p;k <= res;k++)
    58                 f[root][k] = max(f[root][k],f[son[root][i]][k-p]+q);
    59         }
    60     }
    61 }
    62  
    63 int main()
    64 {
    65     scanf("%d",&n);
    66     top = 0;
    67     memset(f,0,sizeof(f));                     //dp数组
    68     memset(son,0,sizeof(son));                 //每个结点的儿子结点
    69     memset(len,0,sizeof(len));                 //每个结点的最高等级
    70     memset(l1,0,sizeof(l1));                   //每个结点的初始等级
    71     memset(data,0,sizeof(data));               //每个节点在字符串序列中的编号
    72     memset(l2,0,sizeof(l2));                   //每个节点升级所需技能点
    73     memset(sc,0,sizeof(sc));                   //每个节点升级所获得加分
    74     for (int i = 1;i <= n;i++)
    75     {
    76         getline(cin,st);
    77         getline(cin,st);
    78         data[i] = findst(st);
    79         getline(cin,st);
    80         if (st!="") y = findst(st);
    81         else y = 0;
    82         son[y][0]++;
    83         son[y][son[y][0]] = data[i];
    84         scanf("%d",&len[data[i]]);
    85         for (int j = 1;j <= len[data[i]];j++)
    86             scanf("%d",&l2[data[i]][j]);
    87         for (int j = 1;j <= len[data[i]];j++)
    88             scanf("%d",&sc[data[i]][j]);
    89  
    90     scanf("%d",&m);
    91     for (int i = 1;i <= n;i++)
    92         scanf("%d",&l1[data[i]]);
    93     doit(0,m);
    94     printf("%d
    ",f[0][m]);
    95 }
    View Code

    题目四:Ural 1018 二叉苹果树
    【问题描述】
    有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
    这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
    我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

    现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
    给定需要保留的树枝数量,求出最多能留住多少苹果。
    【输入格式】
    第1行2个数,N和Q(1<=Q<= N,1<N<=100)。N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。每根树枝上的苹果不超过30000个。
    【输出格式】
    一个数,最多能留住的苹果的数量。
    【输入样例】Etree.in
    5 2
    1 3 1
    1 4 10
    2 3 20
    3 5 20
    【输出样例】Etree.out
    21

    分析:这种二叉树的树型DP还是比较简单的,首先建树,找根,分如下几种情况讨论:

      1、当然容量为0或为叶结点,则返回
      2、当前容量为1,则返回到左儿子边权值和到右儿子边权值中的最大值
      3、当前容量大于1,分三种情况:1)全部给左儿子,2)全部给右儿子,3)左右儿子至少要给一个,剩下的枚举分配求最大值。在三种情况中取一个最大值即可。

    参考代码

      1 //
      2 //  二叉苹果树.cpp
      3 //  树形DP
      4 //
      5 //  Created by TimmyXu on 13-8-3.
      6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
      7 //
      8  
      9 #include <iostream>
     10 #include <cstdio>
     11 #include <cstring>
     12 #include <string>
     13 #include <algorithm>
     14  
     15 using namespace std;
     16  
     17 const int maxn = 100+10;
     18  
     19 int n,q,g2[maxn][maxn],g[maxn][maxn],l[maxn],r[maxn],root,f[maxn][maxn],x,y,du[maxn];
     20 bool vis[maxn];
     21  
     22 void createtree(int x)
     23 {
     24     if (x == 0) return;
     25     vis[x] = true;
     26     for (int i = 1; i<= g2[x][0];i++)
     27         if (!vis[g2[x][i]])
     28         {
     29             if (l[x] == 0) l[x] = g2[x][i];
     30             else r[x] = g2[x][i];
     31         }
     32     createtree(l[x]);
     33     createtree(r[x]);
     34 }
     35  
     36 void doit(int root,int res)
     37 {
     38     if (res == 0)
     39     {
     40         f[root][res] = 0;
     41         return;
     42     }
     43     if (l[root] == 0)
     44     {
     45         f[root][res] = 0;
     46         return;
     47     }
     48     if (f[root][res]>0) return;                                             //记忆化,如果算过则不再计算
     49     if (res == 1)
     50     {
     51         f[root][res] = max(g[root][l[root]],g[root][r[root]]);              //如果只有容量1,则取左右中最大值
     52         return;
     53     }
     54     for (int i = 0;i <= res-2;i++)                                          //如果容量大于1,则先假设左右都被分配到
     55     {
     56         doit(l[root],i);
     57         doit(r[root],res-2-i);
     58         f[root][res] = max(f[root][res],f[l[root]][i]+f[r[root]][res-i-2]);
     59     }
     60  
     61     f[root][res] += g[root][l[root]]+g[root][r[root]];
     62  
     63     doit(l[root],res-1);                                                    //只有左边
     64     f[root][res] = max(f[root][res],f[l[root]][res-1]+g[root][l[root]]);
     65  
     66     doit(r[root],res-1);                                                    //只有右边
     67     f[root][res] = max(f[root][res],f[r[root]][res-1]+g[root][r[root]]);
     68  
     69     return;
     70 }
     71  
     72 int main()
     73 {
     74     scanf("%d%d",&n,&q);
     75     memset(g,0,sizeof(g));
     76     memset(g2,0,sizeof(g2));
     77     memset(f,0,sizeof(f));
     78     memset(l,0,sizeof(l));
     79     memset(r,0,sizeof(r));
     80     memset(vis,false,sizeof(vis));
     81     for (int i = 1;i < n;i++)
     82     {
     83         scanf("%d%d",&x,&y);
     84         scanf("%d",&g[x][y]);
     85         g[y][x] = g[x][y];
     86         g2[x][0]++;
     87         g2[x][g2[x][0]] = y;
     88         g2[y][0]++;
     89         g2[y][g2[y][0]] = x;
     90     }
     91     root = 1;
     92     for (int i = 1;i <= n;i++)
     93         if (g2[i][0] == 2)
     94         {
     95             root  = i;
     96             break;
     97         }
     98     createtree(root);            //建树
     99     doit(root,q);
    100     printf("%d
    ",f[root][q]);
    101 }
    View Code

    题目五:将功补过
    【问题背景】
    作为间谍专家的Elvis Han受窃取X星球军事中心的秘密情报,他已经成功进入军事中心。但是很不幸的是,在他还没有找到任务需要情报的时候就被发现,这时他清楚他不可能完成任务了,不过还有机会将功补过,也就是得到一些不如任务情报有价值的其他情报,如果得到的情报的总价值大于等于任务情报价值,他也不会受到惩罚。很幸运的是他已经得到的军事中心的地图,情报都是隐藏在各个道路上的,但是他只有时间遍历一定数量的路(时间宝贵呀!还要逃跑。。)现在你做为他的助手,给你地图和每个道路情报价值,希望你分析出,看他能不能将功补过。
    【问题描述】
    军事中心是一个严格的二叉树,也就是说,如果有个点可以分道,一定是分出,也只分出2条道路,现在Elvis Han正处在第一个分道处,也就是说树的根结点处。每条道路上都有一个分数,就是这个道路上的情报价值。但是他只有时间走M条路,他的最终情报价值总和就是他所经过的路的情报价值总和(假设他到过的路一定可以把所有情报得到)希望你给出一个方案使得他可以尽量多地获取情报以便将功补过。
    【输入格式】
    在输入文件inform.in中,共有N行:
    第一行:3个数据:N,M,Q(N表示有多少个路口,包括分道和不分道的路口;M表示他可以有时间走的道路总数;Q表示他的任务情报的价值)
    第2~N行:每行3个数据,Xi,Yi,Wi (X,Y表示第I条道路连接的2个路口,W表示这条道路上的情报价值分, 注意,所有数据均在Lonint范围内)
    【输出格式】
    在输出文件inform.out中,共包含2行:
    第一行:输出TRUE/FALSE(注意大小写),表示他是否可以收集够任务情报价值
    第二行:输出一个数据:
    如果他可以完成任务,就输出他收集的情报总价值超过任务情报价值的部分。(正数)
    如果不能完成任务,就输出一个数,表示他不能还差多少分才够任务情报价值。(负数)
    【输入样例1】
    3 1 10
    1 2 10
    1 3 8
    【输出样例1】
    TRUE
    0
    样例说明:(该部分不必输出)

    【输入样例2】
    9 3 49
    6 2 15
    7 2 10
    8 7 6
    7 9 15
    1 3 20
    2 1 10
    4 3 8
    3 5 7
    【输出样例2】
    FALSE
    -4
    样例说明:

    【数据规模】
    对于30%的数据 保证有N<=10
    对于50%的数据 保证有N<=40
    对于全部的数据 保证有 N<=100

    分析:同上题

    参考代码

     1 //
     2 //  将功补过.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <cstdio>
    11 #include <cstring>
    12 #include <string>
    13 #include <algorithm>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 100+10;
    18  
    19 int f[maxn][maxn],n,m,q,x,y,w,g[maxn][maxn],g2[maxn][maxn],l[maxn],r[maxn],root;
    20 bool vis[maxn];
    21  
    22 void createtree(int x)
    23 {
    24     if (x == 0) return;
    25     vis[x] = true;
    26     for (int i = 1;i <= g2[x][0];i++)
    27         if (!vis[g2[x][i]])
    28         {
    29             if (l[x] == 0) l[x] = g2[x][i];
    30             else r[x] = g2[x][i];
    31         }
    32     createtree(l[x]);
    33     createtree(r[x]);
    34 }
    35  
    36 void doit(int x,int res)
    37 {
    38     if (res == 0 || l[x] == 0)
    39     {
    40         f[x][res] = 0;
    41         return;
    42     }
    43     if (f[x][res]>0) return;
    44     if (res == 1)
    45     {
    46         f[x][res] = max(g[x][l[x]],g[x][r[x]]);
    47         return;
    48     }
    49     for (int i = 0;i <= res-2;i++)
    50     {
    51         doit(l[x],i);
    52         doit(r[x],res-i-2);
    53         f[x][res] = max(f[x][res],f[l[x]][i]+f[r[x]][res-i-2]);
    54     }
    55     f[x][res] += g[x][l[x]]+g[x][r[x]];
    56     doit(l[x],res-1);
    57     f[x][res] = max(f[x][res],f[l[x]][res-1]+g[x][l[x]]);
    58     doit(r[x],res-1);
    59     f[x][res] = max(f[x][res],f[r[x]][res-1]+g[x][r[x]]);
    60 }
    61  
    62 int main()
    63 {
    64     scanf("%d",&n,&m,&q);
    65     memset(f,0,sizeof(f));
    66     memset(g,0,sizeof(g));
    67     memset(g2,0,sizeof(g2));
    68     memset(l,0,sizeof(l));
    69     memset(r,0,sizeof(r));
    70     memset(vis,false,sizeof(vis));
    71     for (int i = 1;i < n;i++)
    72     {
    73         scanf("%d%d%d",&x,&y,&w);
    74         g[x][y] = g[y][x] = w;
    75         g2[x][0]++;
    76         g2[x][g2[x][0]] = y;
    77         g2[y][0]++;
    78         g2[y][g2[y][0]] = x;
    79     }
    80     for (int i = 1;i <= n;i++)
    81         if (g2[i][0] == 2)
    82         {
    83             root = i;
    84             break;
    85         }
    86     createtree(root);
    87     doit(root,m);
    88     if (f[root][m] >= q)
    89         printf("TRUE
    %d
    ",f[root][m]-q);
    90     else printf("FALSE
    %d
    ",f[root][m]-q);
    91 }
    View Code

    题目六:加分二叉树
    【问题描述】
    设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
    subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
    若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空
    子树。试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
    (1)tree的最高加分
    (2)tree的前序遍历
    【输入格式】
    第1行:一个整数n(n<30),为节点个数。
    第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
    【输出格式】
    第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
    第2行:n个用空格隔开的整数,为该树的前序遍历。
    【输入样例】
    5
    5 7 1 2 10
    【输出样例】
    145
    3 1 2 4 5

    分析:其实这个是一个区间动态规划,不能算是一个严格的树形DP。对于[l,r]范围内的最大值,每次枚举中间点作为根,递归计算左子树和右子树,算出来的值跟最大值比较即可。

    参考代码

     1 //
     2 //  加分二叉树.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <string>
    11 #include <cstdio>
    12 #include <cstring>
    13 #include <algorithm>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 30+10;
    18  
    19 long long f[maxn][maxn];
    20 int g[maxn][maxn],n,data[maxn];
    21  
    22 void doit(int l,int r)
    23 {
    24     if (f[l][r] > 0return;
    25     if (l == r)
    26     {
    27         f[l][r] = data[l];
    28         g[l][r] = l;
    29         return;
    30     }
    31     if (l > r)
    32     {
    33         f[l][r] = 1;
    34         return;
    35     }
    36     for (int i = l;i <= r;i++)
    37     {
    38         doit(l,i-1);
    39         doit(i+1,r);
    40         if (f[l][i-1]*f[i+1][r]+data[i] > f[l][r])
    41         {
    42             f[l][r] = f[l][i-1]*f[i+1][r]+data[i];
    43             g[l][r] = i;
    44         }
    45     }
    46 }
    47  
    48 void show(int l,int r)
    49 {
    50     if (l > r) return;
    51     if (l == r)
    52     {
    53         printf("%d ",l);
    54         return;
    55     }
    56     printf("%d ",g[l][r]);
    57     show(l,g[l][r]-1);
    58     show(g[l][r]+1,r);
    59 }
    60  
    61 int main()
    62 {
    63     scanf("%d",&n);
    64     for (int i = 1;i <= n;i++)
    65         scanf("%d",&data[i]);
    66     memset(f,0,sizeof(f));
    67     memset(g,0,sizeof(g));
    68     doit(1,n);
    69     printf("%lld
    ",f[1][n]);
    70     show(1,n);
    71     printf("
    ");
    72 }
    View Code

    题目七:Ural 1039 没有上司的晚会
    【背景】
    有个公司要举行一场晚会。为了能玩得开心,公司领导决定:如果邀请了某个人,那么一定不会邀请他的上司(上司的上司,上司的上司的上司……都可以邀请)。
    【问题描述】
    每个参加晚会的人都能为晚会增添一些气氛,求一个邀请方案,使气氛值的和最大。
    【输入格式】
    第1行一个整数N(1<=N<=6000)表示公司的人数。
    接下来N行每行一个整数。第i行的数表示第i个人的气氛值x(-128<=x<=127)。
    接下来每行两个整数L,K。表示第K个人是第L个人的上司。
    输入以0 0结束。
    【输出格式】
    一个数,最大的气氛值和。
    【输入样例】
    7
    1
    1
    1
    1
    1
    1
    1
    1 3
    2 3
    6 4
    7 4
    4 5
    3 5
    0 0
    【输出样例】
    5

    分析:根据题目给出的矛盾关系写出动规方程:
    F[i,0]表示不邀请i,以i为根的子树所获得的最大值
    F[i,1]表示邀请i,以i为根的子树所获得的最大值
    转移如下:

      F[i,0] = Sigma(max(F[j,0],F[j,1])) j是i的儿子 //不邀请i,那么儿子可来可不来,取最大值
      F[i,1] = Sigma(F[j,0]) j是i的儿子 //邀请i,那么儿子不能来,直接求和

    参考代码

     1 //
     2 //  没有上司的晚会.cpp
     3 //  树形DP
     4 //
     5 //  Created by TimmyXu on 13-8-3.
     6 //  Copyright (c) 2013年 TimmyXu. All rights reserved.
     7 //
     8  
     9 #include <iostream>
    10 #include <algorithm>
    11 #include <cstdio>
    12 #include <cstring>
    13 #include <string>
    14  
    15 using namespace std;
    16  
    17 const int maxn = 6000+10;
    18  
    19 int n,root,du[maxn],data[maxn],f[maxn][2],g[maxn][maxn],x,y;
    20  
    21 void doit(int x)
    22 {
    23     f[x][0] = 0;
    24     f[x][1] = data[x];
    25     for (int i = 1;i <= g[x][0];i++)
    26     {
    27         doit(g[x][i]);
    28         f[x][0] += max(f[g[x][i]][0],f[g[x][i]][1]);
    29         f[x][1] += f[g[x][i]][0];
    30     }
    31     return;
    32 }
    33  
    34 int main()
    35 {
    36     scanf("%d",&n);
    37     for (int i = 1;i <= n;i++)
    38         scanf("%d",&data[i]);
    39     memset(f,0,sizeof(f));
    40     memset(g,0,sizeof(g));
    41     memset(du,0,sizeof(du));
    42     for (int i = 1;i < n;i++)
    43     {
    44         scanf("%d%d",&x,&y);
    45         du[x]++;
    46         g[y][0]++;
    47         g[y][g[y][0]]= x;
    48     }
    49     for (int i = 1;i <= n;i++)
    50         if (du[i] == 0)
    51         {
    52             root = i;
    53             break;
    54         }
    55     doit(root);
    56     printf("%d
    ",max(f[root][0],f[root][1]));
    57 }
    View Code

    总结:树形dp的题目,如果单纯给出节点间矛盾关系,或者在二叉树上做背包,都是比较简单的。我目前遇到的题目中,多叉树背包是一个难点,希望自己能继续学习,能拿下。OK,下午二分图最大流搞起!

  • 相关阅读:
    oracle 10g安裝後無法通過http://127.0.0.1:5500/em管理數據庫的問題解決
    Debian系统的安装
    delphi下使用http協議post方式發送xml數據到asp頁面和aspx頁面
    android 应用界面布局
    【转】正则表达式最小化匹配
    android 数据存储操作之 SharedPreferences
    IL程序基本结构
    字符串连接导致sql查询缓慢
    如何不用服务器控件提交数据
    android之 Activity与intent的应用【转载】
  • 原文地址:https://www.cnblogs.com/xsf72006/p/3242875.html
Copyright © 2011-2022 走看看