【题目来源】
https://www.luogu.com.cn/problem/P2015
【题目描述】
有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)。
这棵树共有 N 个结点(叶子点或者树枝分叉点),编号为 1∼N,树根编号一定是 1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 个树枝的树:
cpp
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
留住一个苹果的定义为苹果所在枝条直接与根相连或通过其他枝条间接与根相连。
【输入格式】
第一行 2 个整数 N 和 Q,分别表示表示树的结点数,和要保留的树枝数量。
接下来 N−1 行,每行 3 个整数,描述一根树枝的信息:前 2 个数是它连接的结点的编号,第 3 个数是这根树枝上苹果的数量。
【输出格式】
一个数,最多能留住的苹果的数量。
【输入样例】
5 2
1 3 1
1 4 10
2 3 20
3 5 20
【输出样例】
21
【数据范围】
1⩽Q<N⩽100,每根树枝上的苹果 ⩽3×10^4。
【算法分析】
● 有依赖的背包(树形背包)核心是物品间存在选子必选父的单向依赖关系,依赖结构通常为森林或多叉树,主流解法为树形 DP 结合分组背包自底向上合并;若题目中是双向强依赖、互相绑定的物品组(选其一必全选),则可先用并查集将连通块缩为超级物品,再套用普通 01 背包求解,两种思路对应不同依赖模型,共同构成完整解法体系。
● 有依赖的背包 / 树形背包 = 选儿子必须先选爹 + 把每个子树当成一组物品
● 邻接表:https://blog.csdn.net/hnjzsyjyj/article/details/155789364
● 核心代码解析
(1)f[u][k] 表示在以 u 为根的子树中,保留 k 根树枝能得到的最大苹果数。
(2)f[u][j]=max(f[u][j],f[u][j-i-1]+f[v][i]+w) 中,j-i-1 表示留给自己其他子树的树枝。-1 表示必须保留 u->v 这一条树枝。
【算法代码】
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=105;
vector<pair<int,int>> g[N];
int f[N][N];
int n,V;
void dfs(int u,int fa) {
for(auto t:g[u]) {
int v=t.first;
int w=t.second;
if(v==fa) continue;
dfs(v,u);
for(int j=V; j>=1; j--) {
for(int i=0; i<j; i++) {
f[u][j]=max(f[u][j],f[u][j-i-1]+f[v][i]+w);
}
}
}
}
int main() {
memset(f,0,sizeof f);
cin>>n>>V;
for(int i=1; i<n; i++) {
int x,y,cnt;
cin>>x>>y>>cnt;
g[x].push_back({y,cnt});
g[y].push_back({x,cnt});
}
dfs(1,-1);
cout<<f[1][V]<<endl;
return 0;
}
/*
in:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
out:
21
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/159791882
https://blog.csdn.net/hnjzsyjyj/article/details/159616887
https://blog.csdn.net/hnjzsyjyj/article/details/159650172