树上背包总结
树上背包问题讲解
- 状态定义:通常定义
dp[u][j]表示以节点u为根的子树中,选择j个节点(或边)所能获得的最大价值。 - 递归处理:采用 DFS 后序遍历,先处理所有子节点,计算子树的 dp 值。
- 背包合并:对于每个子节点
v,将v的 dp 值合并到u的 dp 值中。使用倒序循环避免重复计算(类似 01 背包)。 - 初始化:根据问题初始化 dp 数组,通常
dp[u][0] = 0。
关键点
- 状态转移时,考虑从子节点
v的子树中选择k个节点(或边),并加上连接u和v的边权或节点价值。 - 转移方程一般形式:
dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + w),其中-1表示占用一个名额(用于边或节点)。 - 注意树的结构(如二叉树、多叉树)和价值的存储位置(边上或节点上)。
下面针对两个题目进行具体分析。
T1 P2015 二叉苹果树
题目大意
有一棵苹果树,树枝分叉一定是二叉。需要剪枝,保留 Q 条树枝,求最多能留住的苹果数量。树枝的苹果数量存储在边上。
思路分析
状态定义
dp[u][j] 表示以节点 u 为根的子树中,保留 j 条树枝所能获得的最大苹果数。
转移方程
对于每个子节点 v,保留 u 到 v 的树枝需要占用一个名额,因此转移方程为:
dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + v.val)
其中 v.val 是边 (u, v) 上的苹果数量。
过程
- 构建树结构:使用邻接表存储无向图,边权为苹果数。
- 从根节点
1开始 DFS。 - 在 DFS 中,初始化
sz[u] = 1(包括自身)。 - 对于每个子节点
v:- 递归处理
v的子树。 - 更新
sz[u] += sz[v]。 - 倒序循环
j(从sz[u]到0),内层循环k(从0到j - 1),执行转移方程。
- 递归处理
- 输出
dp[1][Q]。
代码特点
- 价值存储在边上,转移时直接使用边权。
- 树枝数量与节点数相关,保留
Q条树枝。 - 倒序更新
j避免重复计算。
T2 P2014 [CTSC1997] 选课
题目大意
有 N 门课程,每门课有学分,且可能有先修课。选择 M 门课程,求最大学分。课程关系构成森林,因此添加虚拟根节点 0。
思路分析
状态定义
dp[u][j] 表示以节点 u 为根的子树中,选择 j 门课程所能获得的最大学分。注意,dp[u][j] 不包括 u 自身的学分(因为 u 的学分会在其父节点处理时被添加)。
转移方程
对于每个子节点 v,选择 v 课程需要占用一个名额,因此转移方程为:
dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + v.val)
其中 v.val 是节点 v 的学分(通过边权传递)。
过程
- 使用邻接表存储有向图(但代码中存储为无向图,通过 DFS 避免回溯)。虚拟根节点
0连接所有无先修课的课程。 - 从虚拟根节点
0开始 DFS。 - 在 DFS 中,初始化
sz[u] = 1(包括自身)。 - 对于每个子节点
v:- 递归处理
v的子树。 - 更新
sz[u] += sz[v]。 - 倒序循环
j(从sz[u]到0),内层循环k(从0到j - 1),执行转移方程。
- 递归处理
- 输出
dp[0][M](因为虚拟根节点0不占课程数)。
代码特点
- 价值存储在节点上,但通过边权传递(子节点的学分作为边权)。
- 添加虚拟根节点
0将森林转化为树。 - 转移时
j - k - 1中的1用于选择子节点v的课程。
本人(KK_SpongeBob)蒟蒻,写不出好文章,但转载请注明原文链接:https://www.cnblogs.com/OIer-QAQ/p/19051428

浙公网安备 33010602011771号