题目描述
有一棵苹果树,如果树枝有分叉,一定是分
2
2
2叉(就是说没有只有
1
1
1个儿子的结点)
这棵树共有
N
N
N个结点(叶子点或者树枝分叉点),编号为
1
−
N
1-N
1−N,树根编号一定是
1
1
1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有
4
4
4个树枝的树。
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
输入格式
第
1
1
1行
2
2
2个数,
N
N
N和
Q
(
1
<
=
Q
<
=
N
,
1
<
N
<
=
100
)
Q(1<=Q<= N,1<N<=100)
Q(1<=Q<=N,1<N<=100)。
N
N
N表示树的结点数,
Q
Q
Q表示要保留的树枝数量。接下来
N
−
1
N-1
N−1行描述树枝的信息。
每行
3
3
3个整数,前两个是它连接的结点的编号。第
3
3
3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过
30000
30000
30000个。
输出格式
一个数,最多能留住的苹果的数量。
输入输出样例
输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出
21
解题思路
这是一道树形
D
P
DP
DP的板子题。
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示以
i
i
i为根节点的子树,保留
j
j
j条树枝时的,保留的最大苹果数。
(这道题有一个隐含的条件,当某条边被保留下来时,从根节点到这条边的路径上的所有边也都必须保留下来)那么状态转移方程为:
- ’ f [ d e p ] [ j ] = m a x ( f [ d e p ] [ j ] , f [ s o n ] [ k ] + f [ d e p ] [ j − k − 1 ] + a [ s o n ] [ d e p ] ) ; f[dep][j]=max(f[dep][j],f[son][k]+f[dep][j-k-1]+a[son][dep]); f[dep][j]=max(f[dep][j],f[son][k]+f[dep][j−k−1]+a[son][dep]);
dep表示当前节点,son是dep的一个子节点,
为什么是
f
[
d
e
p
]
[
i
−
k
−
1
]
f[dep][i-k-1]
f[dep][i−k−1]而不是
f
[
d
e
p
]
[
i
−
k
]
f[dep][i-k]
f[dep][i−k]?
因为保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从uu的子节点vv的子树上留边的话,也要留下
u
,
v
u,v
u,v之间的连边。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,q,x,y,z,a[110][110],s[110][110],f[110][110],c[110],v[110];
void dfs(int dep){
v[dep]=1;//每次循环时,给父结点一个标记,防止无限循环
for(int i=1;i<=c[dep];i++)
{
int son=s[dep][i];
if(v[son]==1)
continue;
v[son]=1;
dfs(son);
for(int j=q;j>0;j--)//要倒序枚举因为这是01背包
{
for(int k=j-1;k>=0;k--)
{
f[dep][j]=max(f[dep][j],f[son][k]+f[dep][j-k-1]+a[son][dep]);
// f[dep][j] 表示以 dep 为 子结点 可以选 j 条边
// f[dep][j-K-1] 表示剩余给 son 结点的 兄弟结点(同一父节点的儿子) j-k-1 条边
// a[son][dep] 表示 son 结点和 dep 结点之间的苹果数
}
}
}
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%d",&x,&y,&z);
a[x][y]=z;
a[y][x]=z;//不知道哪个是父,哪个是子,要双向存
s[x][++c[x]]=y;
s[y][++c[y]]=x;
}
dfs(1);
printf("%d",f[1][q]);
return 0;
}