【洛谷P2015】二叉苹果树

题目描述

有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)

这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

2 5 \ / 3 4 \ / 1 现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

输入输出格式

输入格式:

第1行2个数,N和Q(1<=Q<= N,1<N<=100)。

N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。

每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。

每根树枝上的苹果不超过30000个。

输出格式:

一个数,最多能留住的苹果的数量。

输入输出样例

输入样例#1

5 2

1 3 1

1 4 10

2 3 20

3 5 20

输出样例#1

21

 

 

算法:

树形DP

 

分析:

这道题其实是要我们计算在一棵二叉树上保留一定数量的枝条情况下的最大权值。

这是一个基础的树形DP模板题,在二叉树上进行动规。我们发现对于树上的任意一棵子树,它的根节点和两个子树存在着关系,我们探究一下,树是由递归性质得到的,所以我们首先要把我们得到的数据先在一个数组上建造出来。

 

把树建好了之后,我们就可以探究它的状态转移方程,我们已知n-1条边,为了方便,我们把它设为双向的边。我设len[x][y]和len[y][x]表示x到y的边上权值为len[x][y](或len[y][x])。

接下来,设num[v]表示以v为子节点的那条边的长度,设f[v][k]为以v为根节点的子树中保留k条边的最大权值,设tr[v][ans]为以v为根节点的左右儿子分别是什么,当ans表示1时,他是左儿子,反之,则为右儿子。

 

把一棵树放到一个线性DP里面想的话,就会容易多了。我要求当前的f[v][k],我首先要求得他的左右儿子分担一共k条边的最大值分别是什么,然后相加即可。

 

可列得状态转移方程为:f[v][k]=max(f[v][k],f[tr[v][1]][i]+f[tr[v][2]][k-i-1]+num[v]);

 

这个东西相信不难看懂,接下来我们需要优化程序。既然树可以递归来建造,那么我们是不是也可以用递归来求解呢?嗯,是可以的。

这是我们就可以将普通的DP放到DFS上做,合成了记忆化搜索。因为有一些枝条我在之前已经计算过了,那么我可以直接拿来用。

 

说几点注意的,最后输出答案要输出q+1的情况,因为在计算过程中我们为了方便,将边的问题都转化成父节点或者是子节点问题,所以需要我们在节点数上再+1。

切记len数组要初始化一个负数,在用完其中一条边时,记得把那条边和另外一条等价的边都给删掉。

 

上代码:

 

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 
 5 int n,q,tr[110][3],num[110],len[110][110],f[110][110];        //数据规模不大,int 型已经足够
 6 
 7 inline int read()                                    //读入优化
 8 {
 9     int x=0,f=1;
10     char c=getchar();
11     while (c<48||c>57)
12         f=c=='-'?-1:1,c=getchar();
13     while (c>=48&&c<=57)
14         x=(x<<1)+(x<<3)+(c^48),c=getchar();
15     return x*f;
16 }
17 
18 void buildtree(int v)                            //建树
19 {
20     int i,ans=0;
21     for (i=1;i<=n;i++)
22         if (len[v][i]>=0)                        //有分叉
23         {
24             ans++;                            //记录并判断左右子树
25             tr[v][ans]=i;
26             num[i]=len[v][i];                    //记录长度
27             len[v][i]=len[i][v]=-1;                //删除两条边
28             buildtree(i);                        //递归它的孩子节点
29             if (ans==2)                        //到了就返回
30                 return;
31         }
32 }
33 
34 void dfs(int v,int k)                                //记忆化搜索
35 {
36     if (k==0)                                    //边界
37         f[v][k]=0;
38     else
39     if (tr[v][1]==0&&tr[v][2]==0)                    //到了叶子节点,其值等于原值
40         f[v][k]+=num[v];
41     else
42     {
43         f[v][k]=0;                                //先清零
44         int i,j;
45         for (i=0;i<k;i++)                        //枚举0到k-1的情况
46         {
47             if (!f[tr[v][1]][i])                    //左儿子记忆化
48                 dfs(tr[v][1],i);
49             if (!f[tr[v][2]][k-i-1])                    //右儿子记忆化
50                 dfs(tr[v][2],k-i-1);
51             f[v][k]=max(f[v][k],f[tr[v][1]][i]+f[tr[v][2]][k-i-1]+num[v]);        //状态转移
52         }
53     }
54 }
55 
56 int main()
57 {
58     int i,j;
59     n=read();
60     q=read();
61     for (i=1;i<=n;i++)                        //初始化
62         for (j=1;j<=n;j++)
63             len[i][j]=-1;
64     for (i=1;i<=n-1;i++)
65     {
66         int x=read(),y=read();
67         len[x][y]=len[y][x]=read();
68     }
69     buildtree(1);                            //建树
70     dfs(1,q+1);                            //求解
71     printf("%d",f[1][q+1]);                    //注意,切记是q+1
72     return 0;
73 }

 

 

这是一道二叉树的基础DP,我感觉挺好理解的,主要的模型就是递归建树+记忆化搜索DP,简单记为2个DFS

 

 

嗯,就这样了。

 

posted @ 2018-02-13 15:24  MN2016  阅读(197)  评论(0编辑  收藏  举报