树形 DP 与换根 DP

在树上做 DP:先自底向上收集子树信息,再自顶向下把"子树外"的信息补回来

一、树形 DP 的基本姿势

树没有环,天然适合递归:每个节点的答案只由它的子树决定。后序遍历(先处理完所有儿子再处理父亲)就能自底向上完成 DP:

dfs(u):对每个儿子 v 先 dfs(v),再用 v 的结果更新 u 的状态
生活类比:公司统计每个部门的总人数。总经理不用逐个数员工——每个主管先报上自己部门的人数,上级把下级汇报的数字加起来再加自己即可。这就是一次树形 DP。

二、经典应用:树的重心(P1395 会议)

定义:删掉节点 u 后,剩下的若干连通块中最大的那块越小越好。使"最大连通块大小"最小的 u 称为树的重心。会议室选址、网络中心部署都是这个模型。

删去 u 后连通块分两类:每个儿子的子树(大小 sz[v]),以及"u 上方的其余部分"(大小 n − sz[u])。所以:

maxPart(u) = max( 所有儿子的 sz[v], n − sz[u] ),取 maxPart 最小的 u

一次 dfs 求出所有 sz 即可,O(n)。性质:重心最多 2 个,且若有两个必相邻(题目常要求输出编号最小的那个)。

三、换根 DP:每个点都当一次"根"

问题形态:对每个节点 u,求"以 u 为中心"的某个量(如 u 到所有点的距离和)。对每个 u 单独 dfs 一遍是 O(n²),换根 DP 用两次 dfs 做到 O(n)。

距离和的换根公式

设 f[u] = u 到全树所有点的距离和。当根从 u 移到儿子 v 时(边长为 1):

f[v] = f[u] + (n − sz[v]) − sz[v] = f[u] + n − 2·sz[v]
u v v 的子树:sz[v] 个点(各近 1) 其余 n − sz[v] 个点(各远 1) 根从 u 换成 v
换根的本质:整棵树只有"v 的子树"和"其余部分"的距离变化,O(1) 完成根的移动

两次 dfs 框架

  1. 第一遍 dfs(自底向上):求出每个子树大小 sz 和以 1 为根的 f[1]。
  2. 第二遍 dfs(自顶向下):从根出发,用换根公式把 f 推给每个儿子。

数字走查:链 1—2—3(n = 3,边长 1)

1 2 3 sz=3,f=0+1+2=3 sz=2,f=3+3−2×2=2 sz=1,f=2+3−2×1=3
第一遍:f[1] = 各点深度和 = 3;第二遍:f[2] = 3 + (3−2) − 2 = 2,f[3] = 2 + (3−1) − 1 = 3。中间点 2 距离和最小 ✓

四、模板代码(距离和)

void dfs1(int u, int fa) {
    sz[u] = 1;
    for (int v : g[u]) if (v != fa) {
        dfs1(v, u);
        sz[u] += sz[v];
        f[1] += dep[v];            // f[1] = 所有点深度和(根到各点距离和)
    }
}
void dfs2(int u, int fa) {
    for (int v : g[u]) if (v != fa) {
        f[v] = f[u] + n - 2 * sz[v];   // 换根公式
        dfs2(v, u);
    }
}
推广:换根不止用于距离和。凡是"答案能拆成 子树内贡献 + 子树外贡献"的量(如 P3047 求每个点 k 步内的权和),都可用"第一遍统计子树内 f[u][j]、第二遍补子树外 g[u][j]"的二次扫描法。

五、易错点清单

六、练习

配套小测(5 题,即时判分)