NeFut Logo NeFut
EN 管理员登录

树形动态规划中的换根法与全源路径优化

发布于:2026-05-27 09:17 最后更新:2026-06-06 13:04
#C++ #Tutorial

核心逻辑与数学原理

树形动态规划(Tree DP)的进阶形态中,二次扫描与换根法(Re-rooting / Up-and-Down Tree DP)专用于解决“全源树路径拓扑问题”

在普通树形 DP 中,我们通常假定一个固定的节点(如 $1$ 号节点)为根。此时状态只能沿着拓扑轴自底向上(从子节点向父节点)回溯贡献。如果题目要求计算以树中每一个节点各自作为根节点时的全局统计量(如:以节点 $i$ 为根时,所有节点到 $i$ 的距离之和),暴力对每个节点跑一次 $O(N)$ 的 DFS 会导致 $O(N^2)$ 的灾难性复杂度。

换根法通过两次扫描,在 $O(N)$ 的线性时间内,利用局部状态的拓扑平移,无损映射出全源状态。

1. 第一次扫描:自底向上(Down-to-Up)

$$size[u] = 1 + \sum_{v \in \text{son}(u)} size[v]$$

$$f[u] = \sum_{v \in \text{son}(u)} (f[v] + size[v])$$

2. 第二次扫描:自顶向下(Up-to-Down / 换根演变)

$$g[v] = g[u] - size[v] + (N - size[v]) = g[u] + N - 2 \cdot size[v]$$

通过这个 $O(1)$ 的代数差分方程,我们可以在第二次自顶向下的 DFS 推进中,顺着边直接秒杀出子节点 $v$ 作为全局根时的真实统计量。


状态设计与算法推导

1. 状态空间定义

2. 拓扑推进控制矩阵


C++ 标准源码(NOIP风格)

以下源码解决经典全源树路径和问题:“求树中哪个节点作为根时,所有节点到它的距离之和最小,并输出该最小值”,严格兼容 Linux g++ -O2 编译标准。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int MAXN = 200005;

int n;
vect...  // 省略中间部分

    cout << min_dist << "\n";

    return 0;
}

NOIP 实战避坑指南

1. 第二次扫描时前序与后序位置颠倒

这会导致当前分支在向下递归时,子节点 $v$ 及其下属的所有后代节点使用的依然是初始未赋值的 0 或脏数据。当递归回溯回来时才去更新 $g[v]$,整张全源状态网格会全面崩溃。

2. 换根乘法溢出与常数系数符号写反


经典 NOIP/洛谷 真题

1. 洛谷 P3478 [STA-Station]

2. 洛谷 P2986 [USACO10MAR] Great Cow Gathering G

$$g[v] = g[u] - size[v] \cdot W + (\text{TotalCow} - size[v]) \cdot W$$

通过将简单的节点计数升维映射为权值计数,完美解决带权树上重心的全源快速定位。

原文链接: local://16.3

[h] 返回首页