树上背包总结

树上背包问题讲解

  • 状态定义:通常定义 dp[u][j] 表示以节点 u 为根的子树中,选择 j 个节点(或边)所能获得的最大价值。
  • 递归处理:采用 DFS 后序遍历,先处理所有子节点,计算子树的 dp 值。
  • 背包合并:对于每个子节点 v,将 v 的 dp 值合并到 u 的 dp 值中。使用倒序循环避免重复计算(类似 01 背包)。
  • 初始化:根据问题初始化 dp 数组,通常 dp[u][0] = 0

关键点

  • 状态转移时,考虑从子节点 v 的子树中选择 k 个节点(或边),并加上连接 uv 的边权或节点价值。
  • 转移方程一般形式: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,保留 uv 的树枝需要占用一个名额,因此转移方程为:

dp[u][j] = max(dp[u][j], dp[u][j - k - 1] + dp[v][k] + v.val)

其中 v.val 是边 (u, v) 上的苹果数量。

过程

  1. 构建树结构:使用邻接表存储无向图,边权为苹果数。
  2. 从根节点 1 开始 DFS。
  3. 在 DFS 中,初始化 sz[u] = 1(包括自身)。
  4. 对于每个子节点 v
    • 递归处理 v 的子树。
    • 更新 sz[u] += sz[v]
    • 倒序循环 j(从 sz[u]0),内层循环 k(从 0j - 1),执行转移方程。
  5. 输出 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 的学分(通过边权传递)。

过程

  1. 使用邻接表存储有向图(但代码中存储为无向图,通过 DFS 避免回溯)。虚拟根节点 0 连接所有无先修课的课程。
  2. 从虚拟根节点 0 开始 DFS。
  3. 在 DFS 中,初始化 sz[u] = 1(包括自身)。
  4. 对于每个子节点 v
    • 递归处理 v 的子树。
    • 更新 sz[u] += sz[v]
    • 倒序循环 j(从 sz[u]0),内层循环 k(从 0j - 1),执行转移方程。
  5. 输出 dp[0][M](因为虚拟根节点 0 不占课程数)。

代码特点

  • 价值存储在节点上,但通过边权传递(子节点的学分作为边权)。
  • 添加虚拟根节点 0 将森林转化为树。
  • 转移时 j - k - 1 中的 1 用于选择子节点 v 的课程。

posted @ 2025-08-21 19:44  KK_SpongeBob  阅读(21)  评论(0)    收藏  举报