树上差分与子树求和 (Prefix Sums & Difference on Tree)

自底向上波浪式回溯累加,将高频树上路径区间增减修改开销缩减至 $O(1)$ 极限常数时间

📖 算法原理:点差分打标记与 DFS 回溯求和

在一个树结构中,如果我们要频繁对一条简单路径 $u \leftrightarrow v$ 上的所有节点进行区间加值 $W$,传统的遍历修改开销为 $O(N)$。树上差分(Tree Difference)利用差分和树上前缀性质,将单次修改优化为稳定 $O(1)$ 的常数复杂度。

📐 树上点差分(Vertex Difference)核心操作

设 LCA 是 $u$ 和 $v$ 的最近公共祖先,fa[LCA] 是 LCA 的父节点。我们只需要在以下 4 个位置打上差分标记 diff[]:

• diff[u] += W
• diff[v] += W
• diff[LCA] -= W
• diff[fa[LCA]] -= W (若父节点存在)

自底向上累加原理: 打完标记后,我们运行一次自底向上(深度优先后序遍历)的子树求和。对于任意节点 $i$,其最终的值(前缀和)sum[i] 等于其自身差分值加上所有子节点的子树前缀和之和:

$sum[i] = diff[i] + \sum_{child \in children(i)} sum[child]$

💡 直观定理:通过此求和,差分标记的贡献会沿着树枝向上辐射,且在 LCA 之上的节点和其它不相关支路节点会因 LCA 和 fa[LCA] 处的减值标记而被完美抵消,最终只有 $u \leftrightarrow v$ 路径上的节点获得了修改值!

请直接在下方图上点击选定两个节点确立区间路径,然后点击“打差分标记”。
🕸️ 树上点差分自底向上回溯空间 (Tree Difference Shifting Playground)
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]; // 累加所有子树的前缀和
    }
}
📋 差分与树上求步骤步骤