树的最大独立集是树中互不相邻节点构成的最大集合;因暴力枚举为o(2ⁿ)不可行,而树无环、可自底向上递推,故用树形dp:设dpu表示u不选/选时子树最大独立集大小,转移为dpu=1+∑dpv、dpu=∑max(dpv,dpv),需后序遍历,最终答案为max(dproot,dproot)。

什么是树的最大独立集,为什么用树形DP
最大独立集指树中互不相邻的节点构成的最大集合。暴力枚举所有子集是 O(2ⁿ) 的,不可行;而树没有环,天然支持自底向上递推——每个节点的最优解只依赖于子节点的两种状态:选自己,或不选自己。这正是树形DP的典型结构:dp[u][0] 表示以 u 为根的子树中,u 不选 时的最大独立集大小;dp[u][1] 表示 u 选 时的最大值。
状态转移怎么写,关键约束在哪
核心约束只有一条:若父节点选了,子节点就不能选;若父节点没选,子节点可选可不选(取更优者)。
-
dp[u][1] = 1 + Σ dp[v][0](v 是 u 的每个子节点) dp[u][0] = Σ max(dp[v][0], dp[v][1])
注意点:
- 必须后序遍历(DFS 回溯时更新),保证子节点状态已计算完毕
- 初始化:叶子节点满足
dp[u][1] = 1,dp[u][0] = 0 - 若树不连通(森林),需对每个未访问过的根节点单独 DFS
建树和DFS实现要注意什么
C++ 中常用邻接表存无向树,但 DFS 时要避免「回到父节点」造成死循环:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 传入当前节点
u和其父节点fa,遍历邻居时跳过fa - 不要用
visited[]数组代替fa参数,否则在多叉树中容易误判回边 - 边数为
n-1,确保输入合法(否则不是树)
简单示例(链式前向星建图):
void dfs(int u, int fa) {
dp[u][1] = 1;
dp[u][0] = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (v == fa) continue;
dfs(v, u);
dp[u][1] += dp[v][0];
dp[u][0] += max(dp[v][0], dp[v][1]);
}
}
结果怎么取,边界和性能有啥坑
最终答案是 max(dp[root][0], dp[root][1]),root 可任取(比如节点 1)。常见疏漏:
- 忘记初始化
dp数组(尤其多测时未清零) - 把树当有向图处理,导致父子关系错乱
- 使用 vector
> 存 dp,频繁 push_back 引发常数开销(静态数组或 vector.resize(n+1, {0,0}) 更稳) - n=1 时直接返回 1,但代码里若没特判,DFS 仍能正确运行(因为无子节点,两个状态就是 0 和 1)
树形DP本身是 O(n) 的,但递归深度可能接近 1e5,某些编译器默认栈空间不足——必要时手动扩栈或改用 BFS 模拟后序(少见,一般交题平台已优化)。
实际写的时候,别急着套模板,先手推三节点链(1-2-3)验证两层转移是否符合直觉:选2则得1,不选2则可选1和3得2——这才是对的。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










