C++ 树形动态规划:从原理到实战深度解析
树形动态规划(Tree DP)是处理树形结构问题的核心算法分支。它利用树的天然递归特性,将子树的最优解向上合并,最终得到整棵树的全局最优解。在算法竞赛和面试中,这类问题出现频率极高。
一、初识树形 DP:它解决什么问题?
很多开发者在学习完线性 DP 或区间 DP 后,面对树形 DP 会感到困惑。其实核心区别在于状态依赖的方向:普通 DP 通常按线性顺序推导,而树形 DP 必须遵循'自底向上'的原则——先求解所有子节点的状态,再合并到父节点。
典型特征
- 数据结构是树:问题载体为树或森林,节点间存在明确的父子层级关系。
- 递归求解子树:任意节点 u 的最优解依赖于其所有子节点 v 的最优解。
- 无后效性:子树的解仅取决于自身结构,不受父节点后续操作影响。
以'树的最大独立集'为例:给定一棵树,选择若干节点使得任意两个选中节点不相邻,求最大权值和。这里节点 u 的选择状态直接决定了子节点 v 能否被选,必须先知道子节点的最优情况才能计算 u。
前置知识
- 树的存储:常用邻接表,适合将无向树转化为有向父子关系。
- 树的遍历:深度优先搜索(DFS)是核心,用于实现自底向上的遍历。
- 递归思想:理解'递归处理子树,回溯合并结果'的逻辑。
二、树形 DP 核心框架:四步走
树形 DP 的实现框架高度固定,可以总结为'建图→状态定义→递归遍历→状态合并'。
步骤 1:树的存储(邻接表)
树通常以无向图形式给出,需要用邻接表存储。遍历时需标记父节点,避免从子节点回退到父节点造成死循环。
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e5 + 5;
vector<int> adj[MAXN]; // 邻接表
bool vis[MAXN]; // 标记是否访问过
// 添加边
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
步骤 2:状态定义
状态通常定义为 dp[u][k],其中 u 是当前节点,k 是该节点的状态维度(如选/不选、选 m 个节点等)。状态定义是难点,必须覆盖子树的所有必要信息,且能通过子节点推导父节点。


