树形DP
树形动态规划是一种在树结构上进行动态规划的算法技术,它利用树的递归性质来解决各种优化问题。以下是树形DP的详细解析:
基本概念
树形DP的核心思想是自底向上或后序遍历处理树结构,即在处理父节点之前先处理完所有子节点。
实现步骤
- 确定状态表示:定义dp[u][state]表示以u为根的子树在某种状态下的最优解
- 状态转移方程:根据子节点的状态推导父节点的状态
- 递归实现:通常使用DFS遍历树结构
- 边界条件:处理叶子节点的初始状态
经典问题示例
问题1:没有上司的舞会(最大独立集)
问题描述:一棵树,选择不相邻的节点使权值和最大。
解法:
def tree_dp(u, parent):dp = [[0]*2 for _ in range(n)] # dp[u][0/1]表示u不选/选时的最大值dp[u][1] = weight[u] # 选择当前节点的初始值for v in tree[u]:if v == parent:continuetree_dp(v, u)dp[u][0] += max(dp[v][0], dp[v][1]) # 不选u时,子节点可选可不选dp[u][1] += dp[v][0] # 选u时,子节点不能选
问题2:树的最长路径(直径)
解法:
def diameter_dfs(u, parent):max_depth1 = max_depth2 = 0for v in tree[u]:if v == parent:continuedepth = diameter_dfs(v, u) + 1if depth > max_depth1:max_depth2 = max_depth1max_depth1 = depthelif depth > max_depth2:max_depth2 = depthglobal max_diametermax_diameter = max(max_diameter, max_depth1 + max_depth2)return max_depth1
常见问题类型
- 最大独立集:选择不相邻节点使权值和最大
- 最小点覆盖:选择最少的点覆盖所有边
- 树的重心:找到一个节点使删除后最大子树最小
- 树的直径:树上最远两点距离
- 树形背包:在树上进行分组背包问题
优化技巧
- 记忆化搜索:避免重复计算
- 二次扫描:对于需要父节点信息的题目
- 换根DP:当需要以每个节点为根进行计算时
时间复杂度
通常为O(n),其中n为节点数,因为每个节点只被处理一次。
树形DP的关键在于正确设计状态表示和转移方程,充分利用树的递归性质,通过子问题的解来构建更大问题的解