二叉苹果树
题目描述
有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)
这棵树共有 N N N 个结点(叶子点或者树枝分叉点),编号为 1 ∼ N 1 \sim N 1∼N,树根编号一定是 1 1 1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 4 4 个树枝的树:
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
输入格式
第一行 2 2 2 个整数 N N N 和 Q Q Q,分别表示表示树的结点数,和要保留的树枝数量。
接下来 N − 1 N-1 N−1 行,每行 3 3 3 个整数,描述一根树枝的信息:前 2 2 2 个数是它连接的结点的编号,第 3 3 3 个数是这根树枝上苹果的数量。
输出格式
一个数,最多能留住的苹果的数量。
样例 #1
样例输入 #1
5 2
1 3 1
1 4 10
2 3 20
3 5 20
样例输出 #1
21
提示
1 ⩽ Q < N ⩽ 100 1 \leqslant Q < N \leqslant 100 1⩽Q<N⩽100,每根树枝上的苹果 ⩽ 3 × 1 0 4 \leqslant 3 \times 10^4 ⩽3×104。
分析
树形 d p dp dp 经典题。这里保留边不太好处理,我打代码时都默认求的是保留 m + 1 m+1 m+1 个点。设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以第 i i i 个节点为根的子树保留 j j j 个节点所能得到的最大值,那么递推方程也比较好想:
若此节点无儿子, d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0 。
否则,设
s
o
n
0
son_0
son0 为该节点左儿子,
s
o
n
1
son_1
son1 为该节点右儿子。现在分配
l
l
l 个节点给左儿子,则分配
j
−
l
−
1
j-l-1
j−l−1 个给右儿子(自己本身要保留),则当两个儿子都保留时,递推方程为:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
]
,
d
p
[
s
o
n
0
]
[
l
]
+
d
p
[
s
o
n
1
]
[
j
−
l
+
1
]
+
v
a
l
[
s
o
n
0
]
+
v
a
l
[
s
o
n
1
]
)
dp[i][j]=max(dp[i][j],dp[son_0][l]+dp[son_1][j-l+1]+val[son_0]+val[son_1])
dp[i][j]=max(dp[i][j],dp[son0][l]+dp[son1][j−l+1]+val[son0]+val[son1])
这里
l
≥
1
,
j
−
l
−
1
≥
1
l\ge 1,j-l-1\ge 1
l≥1,j−l−1≥1 ,解得
1
≤
l
≤
l
−
2
1\le l\le l-2
1≤l≤l−2 。
当只保留左儿子,
d
p
[
i
]
[
j
]
=
d
p
[
s
o
n
0
]
[
j
−
1
]
+
v
a
l
[
s
o
n
0
]
dp[i][j]=dp[son_0][j-1]+val[son_0]
dp[i][j]=dp[son0][j−1]+val[son0] 。
当只保留右儿子,
d
p
[
i
]
[
j
]
=
d
p
[
s
o
n
1
]
[
j
−
1
]
+
v
a
l
[
s
o
n
1
]
dp[i][j]=dp[son_1][j-1]+val[son_1]
dp[i][j]=dp[son1][j−1]+val[son1] 。
Code
#include<bits/stdc++.h>
using namespace std;
struct nod{
int to,d;
};
int n,m,x,y,z,f[110],dp[110][110],son[110][2],val[110],depth[110];
vector<nod>w[110];
vector<int>de[110];
void build(int now,int fa){
depth[now]=depth[fa]+1;
de[depth[now]].push_back(now);
for(int i=0;i<w[now].size();++i)
if(w[now][i].to!=fa){
val[w[now][i].to]=w[now][i].d;
f[w[now][i].to]=now;
if(!son[now][0]) son[now][0]=w[now][i].to;
else son[now][1]=w[now][i].to;
build(w[now][i].to,now);
}
}
int main(){
cin>>n>>m;
m++;
for(int i=1;i<n;++i){
cin>>x>>y>>z;
w[x].push_back(nod{y,z}),w[y].push_back(nod{x,z});
}
build(1,1);
for(int i=n;i>=1;--i)//按深度从深到浅dp
for(int j=0;j<de[i].size();++j){
int rt=de[i][j];
if(!son[rt][0]) continue;//叶子节点,忽略
for(int k=2;k<=m;++k){
dp[rt][k]=max(dp[son[rt][0]][k-1]+val[son[rt][0]],dp[son[rt][1]][k-1]+val[son[rt][1]]);
for(int l=1;l<k-1;++l) dp[rt][k]=max(dp[rt][k],dp[son[rt][0]][l]+dp[son[rt][1]][k-l-1]+val[son[rt][0]]+val[son[rt][1]]);
}
}
cout<<dp[1][m]<<endl;
return 0;
}