自底向上波浪式回溯累加,将高频树上路径区间增减修改开销缩减至 $O(1)$ 极限常数时间
在一个树结构中,如果我们要频繁对一条简单路径 $u \leftrightarrow v$ 上的所有节点进行区间加值 $W$,传统的遍历修改开销为 $O(N)$。树上差分(Tree Difference)利用差分和树上前缀性质,将单次修改优化为稳定 $O(1)$ 的常数复杂度。
设 LCA 是 $u$ 和 $v$ 的最近公共祖先,fa[LCA] 是 LCA 的父节点。我们只需要在以下 4 个位置打上差分标记 diff[]:
diff[u] += W diff[v] += W diff[LCA] -= W diff[fa[LCA]] -= W (若父节点存在)
自底向上累加原理:
打完标记后,我们运行一次自底向上(深度优先后序遍历)的子树求和。对于任意节点 $i$,其最终的值(前缀和)sum[i] 等于其自身差分值加上所有子节点的子树前缀和之和:
💡 直观定理:通过此求和,差分标记的贡献会沿着树枝向上辐射,且在 LCA 之上的节点和其它不相关支路节点会因 LCA 和 fa[LCA] 处的减值标记而被完美抵消,最终只有 $u \leftrightarrow v$ 路径上的节点获得了修改值!
int diff[MAXN], sum[MAXN];
// O(1) 在路径 u-v 上加权重 x
void addPath(int u, int v, int x) {
int lca = getLCA(u, v);
diff[u] += x;
diff[v] += x;
diff[lca] -= x;
if (fa[lca]) {
diff[fa[lca]] -= x; // 抵消 LCA 以上的累加贡献
}
}
// DFS 自底向上求和得出各个点的最终累计值
void dfsSum(int u, int p) {
sum[u] = diff[u];
for (int v : g[u]) {
if (v == p) continue;
dfsSum(v, u);
sum[u] += sum[v]; // 累加所有子树的前缀和
}
}