树链剖分(Heavy-Light Decomposition, HLD)

我来详细讲解 树链剖分(Heavy-Light Decomposition, HLD),这是一种将树结构转化为线性序列的强大技术,使得树上的路径问题可以转化为区间问题,从而用线段树等数据结构高效解决。


核心思想

树链剖分将一棵树分解为若干条重链 (Heavy Path),使得从任意节点到根的路径最多经过 O(log n) 条重链。

关键概念

  • 重儿子:子树大小最大的儿子

  • 轻儿子:其他儿子

  • 重边:连接节点与其重儿子的边

  • 重链:由重边组成的链

  • 轻边:连接节点与其轻儿子的边

    树结构:
    1
    /
    2 3
    /| |
    4 5 6
    / /
    7 8 9

    重链剖分(假设子树大小:1>2>3,2>4>5,4>7,6>8>9):
    重链1:1 - 2 - 4 - 7(主链)
    重链2:3 - 6 - 8
    重链3:5
    重链4:9

    性质:任意路径 u→v 可以拆分为 O(log n) 段连续的 DFS 序区间


完整实现:路径修改 + 路径查询

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

int n, m, root;
int mod;

// 原图
vector<int> g[N];
int w[N];           // 节点权值

// 树链剖分数组
int fa[N];          // 父节点
int dep[N];         // 深度
int siz[N];         // 子树大小
int son[N];         // 重儿子(0表示无)

int top[N];         // 所在重链的顶端节点
int dfn[N];         // DFS序(时间戳)
int rnk[N];         // dfn的逆:rnk[dfn[u]] = u
int wval[N];        // 按DFS序排列的权值(用于线段树)

int tim;            // 时间戳计数器

// ========== 第一步:DFS1 求 fa, dep, siz, son ==========
void dfs1(int u, int f) {
    fa[u] = f;
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    son[u] = 0;
    
    for (int v : g[u]) {
        if (v == f) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]]) son[u] = v;  // 更新重儿子
    }
}

// ========== 第二步:DFS2 求 top, dfn, 剖分重链 ==========
void dfs2(int u, int t) {
    top[u] = t;
    dfn[u] = ++tim;
    rnk[tim] = u;
    wval[tim] = w[u];   // 按DFS序存储权值
    
    if (!son[u]) return;    // 叶子节点
    
    // 重儿子先走,保证重链上DFS序连续
    dfs2(son[u], t);
    
    // 轻儿子各自成链
    for (int v : g[u]) {
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);     // 轻儿子的top是自己
    }
}

// ========== 线段树部分(区间修改、区间查询) ==========
struct Node {
    int l, r;
    long long sum, add;
} tr[N * 4];

void pushup(int u) {
    tr[u].sum = (tr[u<<1].sum + tr[u<<1|1].sum) % mod;
}

void apply(int u, long long v) {
    tr[u].sum = (tr[u].sum + (tr[u].r - tr[u].l + 1) * v) % mod;
    tr[u].add = (tr[u].add + v) % mod;
}

void pushdown(int u) {
    if (tr[u].add) {
        apply(u<<1, tr[u].add);
        apply(u<<1|1, tr[u].add);
        tr[u].add = 0;
    }
}

void build(int u, int l, int r) {
    tr[u] = {l, r, wval[l], 0};
    if (l == r) return;
    int mid = l + r >> 1;
    build(u<<1, l, mid);
    build(u<<1|1, mid+1, r);
    pushup(u);
}

void modify(int u, int l, int r, long long v) {
    if (l <= tr[u].l && tr[u].r <= r) {
        apply(u, v);
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u<<1, l, r, v);
    if (r > mid) modify(u<<1|1, l, r, v);
    pushup(u);
}

long long query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    long long res = 0;
    if (l <= mid) res = (res + query(u<<1, l, r)) % mod;
    if (r > mid) res = (res + query(u<<1|1, l, r)) % mod;
    return res;
}

// ========== 树链剖分核心操作:路径修改与查询 ==========

// 将路径 u→v 上的所有节点加 z
void pathModify(int u, int v, long long z) {
    z %= mod;
    while (top[u] != top[v]) {          // 不在同一条重链
        if (dep[top[u]] < dep[top[v]]) swap(u, v);  // 保证u所在链更深
        modify(1, dfn[top[u]], dfn[u], z);  // 修改整条重链
        u = fa[top[u]];                 // 跳到链头的父节点
    }
    // 现在在同一条重链上
    if (dep[u] > dep[v]) swap(u, v);    // 保证u是LCA
    modify(1, dfn[u], dfn[v], z);       // 修改最后一段
}

// 查询路径 u→v 上的权值和
long long pathQuery(int u, int v) {
    long long res = 0;
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        res = (res + query(1, dfn[top[u]], dfn[u])) % mod;
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    res = (res + query(1, dfn[u], dfn[v])) % mod;
    return res;
}

// ========== 子树操作(利用DFS序性质) ==========
// 子树所有节点的DFS序是连续的 [dfn[u], dfn[u] + siz[u] - 1]

void subtreeModify(int u, long long z) {
    modify(1, dfn[u], dfn[u] + siz[u] - 1, z % mod);
}

long long subtreeQuery(int u) {
    return query(1, dfn[u], dfn[u] + siz[u] - 1);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n >> m >> root >> mod;
    
    for (int i = 1; i <= n; i++) cin >> w[i];
    
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    // 树链剖分
    dfs1(root, 0);
    dfs2(root, root);
    
    // 建线段树
    build(1, 1, n);
    
    while (m--) {
        int op, x, y;
        long long z;
        cin >> op;
        
        if (op == 1) {          // 路径修改
            cin >> x >> y >> z;
            pathModify(x, y, z);
        } else if (op == 2) {   // 路径查询
            cin >> x >> y;
            cout << pathQuery(x, y) << '\n';
        } else if (op == 3) {   // 子树修改
            cin >> x >> z;
            subtreeModify(x, z);
        } else {                // 子树查询
            cin >> x;
            cout << subtreeQuery(x) << '\n';
        }
    }
    return 0;
}

关键过程图解

路径查询/修改的拆分过程

复制代码
查询路径 7 → 9:

7 在重链 [1-2-4-7] 上,top[7]=1
9 在重链 [3-6-9] 上,top[9]=3

第一次循环:
  top[7]=1, top[9]=3, 不同
  dep[1]=1 < dep[3]=2, 交换:u=9, v=7
  查询 [dfn[3], dfn[9]] = [5,7](重链3-6-8-9?不对,看具体剖分)
  u = fa[3] = 1

第二次循环:
  top[1]=1, top[7]=1, 相同!
  查询 [dfn[1], dfn[7]] = [1,4]

总共两段,O(log n) 次线段树操作

进阶:LCA(最近公共祖先)

树链剖分可以 O(log n) 求 LCA:

cpp 复制代码
int lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}

进阶:维护边权

将边权转化为点权:每条边的权值赋给子节点

cpp 复制代码
// 边 (u,v,w),假设 v 是 u 的儿子
// 把 w 存在 v 上
w[v] = 边权;

// 查询 u→v 路径上的最大边权(u是v的祖先)
int queryEdge(int u, int v) {  // u是v的祖先
    int res = 0;
    while (top[u] != top[v]) {
        res = max(res, seg.queryMax(dfn[top[v]], dfn[v]));
        v = fa[top[v]];
    }
    // 现在同一条链,u是祖先
    if (u != v)  // 排除u本身(边权在子节点上)
        res = max(res, seg.queryMax(dfn[u]+1, dfn[v]));
    return res;
}

进阶:换根操作

维护以不同节点为根时的子树信息。

cpp 复制代码
// 判断 u 是否是 v 的祖先
bool isAncestor(int u, int v) {
    return dfn[u] <= dfn[v] && dfn[v] < dfn[u] + siz[u];
}

// 换根后,x的子树是什么?
// 设当前根为root,求x的子树
int getRootedSubtree(int root, int x) {
    if (root == x) return 1;  // 整棵树
    if (!isAncestor(x, root)) {
        // x的子树不变
        return seg.query(dfn[x], dfn[x] + siz[x] - 1);
    }
    // root在x的某个儿子的子树中,x的子树是整棵树减去那个儿子的子树
    int y = jump(root, dep[root] - dep[x] - 1);  // 从root往上跳到x的儿子
    return total - seg.query(dfn[y], dfn[y] + siz[y] - 1);
}

// 从u向上跳k步
int jump(int u, int k) {
    while (k) {
        if (dfn[u] - dfn[top[u]] + 1 <= k) {
            k -= dfn[u] - dfn[top[u]] + 1;
            u = fa[top[u]];
        } else {
            return rnk[dfn[u] - k];
        }
    }
    return u;
}

完整封装模板

cpp 复制代码
template<typename T, typename SegTree>
struct TreeChain {
    int n, root;
    vector<vector<int>> g;
    vector<int> fa, dep, siz, son, top, dfn, rnk;
    vector<T> w, wval;
    SegTree seg;
    int tim;
    
    TreeChain(int n = 0, int root = 1) { init(n, root); }
    
    void init(int n_, int root_) {
        n = n_; root = root_;
        g.assign(n + 1, {});
        fa = dep = siz = son = top = dfn = rnk = vector<int>(n + 1);
        w = wval = vector<T>(n + 1);
        tim = 0;
    }
    
    void addEdge(int u, int v) {
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    void setWeight(int u, T val) { w[u] = val; }
    
    void build() {
        dfs1(root, 0);
        dfs2(root, root);
        seg.build(wval, 1, n);
    }
    
    void dfs1(int u, int f) {
        fa[u] = f; dep[u] = dep[f] + 1; siz[u] = 1; son[u] = 0;
        for (int v : g[u]) if (v != f) {
            dfs1(v, u);
            siz[u] += siz[v];
            if (siz[v] > siz[son[u]]) son[u] = v;
        }
    }
    
    void dfs2(int u, int t) {
        top[u] = t; dfn[u] = ++tim; rnk[tim] = u; wval[tim] = w[u];
        if (!son[u]) return;
        dfs2(son[u], t);
        for (int v : g[u]) if (v != fa[u] && v != son[u])
            dfs2(v, v);
    }
    
    // 路径操作
    void pathModify(int u, int v, T val) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            seg.modify(dfn[top[u]], dfn[u], val);
            u = fa[top[u]];
        }
        if (dep[u] > dep[v]) swap(u, v);
        seg.modify(dfn[u], dfn[v], val);
    }
    
    T pathQuery(int u, int v) {
        T res = 0;
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            res = res + seg.query(dfn[top[u]], dfn[u]);
            u = fa[top[u]];
        }
        if (dep[u] > dep[v]) swap(u, v);
        return res + seg.query(dfn[u], dfn[v]);
    }
    
    // 子树操作
    void subtreeModify(int u, T val) {
        seg.modify(dfn[u], dfn[u] + siz[u] - 1, val);
    }
    
    T subtreeQuery(int u) {
        return seg.query(dfn[u], dfn[u] + siz[u] - 1);
    }
    
    int lca(int u, int v) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            u = fa[top[u]];
        }
        return dep[u] < dep[v] ? u : v;
    }
};

时间复杂度分析

操作 时间复杂度 说明
树链剖分(两次DFS) O(n) 预处理
路径修改/查询 O(log² n) 最多 O(log n) 条重链,每条线段树 O(log n)
子树修改/查询 O(log n) DFS序连续,直接线段树
LCA O(log n) 跳重链
换根子树查询 O(log n) 配合额外判断

注意:如果线段树换成 O(1) 查询的数据结构(如ST表),路径查询可优化到 O(log n)。


应用场景总结

应用场景 关键技巧
树上路径修改/查询 拆分为重链区间
子树修改/查询 利用DFS序连续性
边权维护 下放到子节点
换根问题 分类讨论root位置
树上线段树合并 配合DSU on tree
动态树问题 结合Link-Cut Tree

与其他算法的对比

算法 功能 时间复杂度 适用场景
树链剖分 路径/子树操作 O(log² n) 静态树,频繁路径查询
LCT (Link-Cut Tree) 动态树,换根、连边、断边 均摊 O(log n) 动态树结构
欧拉序+线段树 子树操作 O(log n) 只有子树操作,无路径
倍增法 LCA、祖先查询 O(log n) 预处理,O(1) 查询 只需LCA,无需修改

树链剖分是静态树上路径问题的标准解法 ,代码规整、效率优秀,是竞赛中的必备技能! 我来详细讲解 树链剖分(Heavy-Light Decomposition, HLD),这是一种将树结构转化为线性序列的强大技术,使得树上的路径问题可以转化为区间问题,从而用线段树等数据结构高效解决。


核心思想

树链剖分将一棵树分解为若干条重链 (Heavy Path),使得从任意节点到根的路径最多经过 O(log n) 条重链。

关键概念

  • 重儿子:子树大小最大的儿子

  • 轻儿子:其他儿子

  • 重边:连接节点与其重儿子的边

  • 重链:由重边组成的链

  • 轻边:连接节点与其轻儿子的边

    树结构:
    1
    /
    2 3
    /| |
    4 5 6
    / /
    7 8 9

    重链剖分(假设子树大小:1>2>3,2>4>5,4>7,6>8>9):
    重链1:1 - 2 - 4 - 7(主链)
    重链2:3 - 6 - 8
    重链3:5
    重链4:9

    性质:任意路径 u→v 可以拆分为 O(log n) 段连续的 DFS 序区间


完整实现:路径修改 + 路径查询

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

int n, m, root;
int mod;

// 原图
vector<int> g[N];
int w[N];           // 节点权值

// 树链剖分数组
int fa[N];          // 父节点
int dep[N];         // 深度
int siz[N];         // 子树大小
int son[N];         // 重儿子(0表示无)

int top[N];         // 所在重链的顶端节点
int dfn[N];         // DFS序(时间戳)
int rnk[N];         // dfn的逆:rnk[dfn[u]] = u
int wval[N];        // 按DFS序排列的权值(用于线段树)

int tim;            // 时间戳计数器

// ========== 第一步:DFS1 求 fa, dep, siz, son ==========
void dfs1(int u, int f) {
    fa[u] = f;
    dep[u] = dep[f] + 1;
    siz[u] = 1;
    son[u] = 0;
    
    for (int v : g[u]) {
        if (v == f) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]]) son[u] = v;  // 更新重儿子
    }
}

// ========== 第二步:DFS2 求 top, dfn, 剖分重链 ==========
void dfs2(int u, int t) {
    top[u] = t;
    dfn[u] = ++tim;
    rnk[tim] = u;
    wval[tim] = w[u];   // 按DFS序存储权值
    
    if (!son[u]) return;    // 叶子节点
    
    // 重儿子先走,保证重链上DFS序连续
    dfs2(son[u], t);
    
    // 轻儿子各自成链
    for (int v : g[u]) {
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);     // 轻儿子的top是自己
    }
}

// ========== 线段树部分(区间修改、区间查询) ==========
struct Node {
    int l, r;
    long long sum, add;
} tr[N * 4];

void pushup(int u) {
    tr[u].sum = (tr[u<<1].sum + tr[u<<1|1].sum) % mod;
}

void apply(int u, long long v) {
    tr[u].sum = (tr[u].sum + (tr[u].r - tr[u].l + 1) * v) % mod;
    tr[u].add = (tr[u].add + v) % mod;
}

void pushdown(int u) {
    if (tr[u].add) {
        apply(u<<1, tr[u].add);
        apply(u<<1|1, tr[u].add);
        tr[u].add = 0;
    }
}

void build(int u, int l, int r) {
    tr[u] = {l, r, wval[l], 0};
    if (l == r) return;
    int mid = l + r >> 1;
    build(u<<1, l, mid);
    build(u<<1|1, mid+1, r);
    pushup(u);
}

void modify(int u, int l, int r, long long v) {
    if (l <= tr[u].l && tr[u].r <= r) {
        apply(u, v);
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u<<1, l, r, v);
    if (r > mid) modify(u<<1|1, l, r, v);
    pushup(u);
}

long long query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    long long res = 0;
    if (l <= mid) res = (res + query(u<<1, l, r)) % mod;
    if (r > mid) res = (res + query(u<<1|1, l, r)) % mod;
    return res;
}

// ========== 树链剖分核心操作:路径修改与查询 ==========

// 将路径 u→v 上的所有节点加 z
void pathModify(int u, int v, long long z) {
    z %= mod;
    while (top[u] != top[v]) {          // 不在同一条重链
        if (dep[top[u]] < dep[top[v]]) swap(u, v);  // 保证u所在链更深
        modify(1, dfn[top[u]], dfn[u], z);  // 修改整条重链
        u = fa[top[u]];                 // 跳到链头的父节点
    }
    // 现在在同一条重链上
    if (dep[u] > dep[v]) swap(u, v);    // 保证u是LCA
    modify(1, dfn[u], dfn[v], z);       // 修改最后一段
}

// 查询路径 u→v 上的权值和
long long pathQuery(int u, int v) {
    long long res = 0;
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        res = (res + query(1, dfn[top[u]], dfn[u])) % mod;
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    res = (res + query(1, dfn[u], dfn[v])) % mod;
    return res;
}

// ========== 子树操作(利用DFS序性质) ==========
// 子树所有节点的DFS序是连续的 [dfn[u], dfn[u] + siz[u] - 1]

void subtreeModify(int u, long long z) {
    modify(1, dfn[u], dfn[u] + siz[u] - 1, z % mod);
}

long long subtreeQuery(int u) {
    return query(1, dfn[u], dfn[u] + siz[u] - 1);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n >> m >> root >> mod;
    
    for (int i = 1; i <= n; i++) cin >> w[i];
    
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    // 树链剖分
    dfs1(root, 0);
    dfs2(root, root);
    
    // 建线段树
    build(1, 1, n);
    
    while (m--) {
        int op, x, y;
        long long z;
        cin >> op;
        
        if (op == 1) {          // 路径修改
            cin >> x >> y >> z;
            pathModify(x, y, z);
        } else if (op == 2) {   // 路径查询
            cin >> x >> y;
            cout << pathQuery(x, y) << '\n';
        } else if (op == 3) {   // 子树修改
            cin >> x >> z;
            subtreeModify(x, z);
        } else {                // 子树查询
            cin >> x;
            cout << subtreeQuery(x) << '\n';
        }
    }
    return 0;
}

关键过程图解

路径查询/修改的拆分过程

复制代码
查询路径 7 → 9:

7 在重链 [1-2-4-7] 上,top[7]=1
9 在重链 [3-6-9] 上,top[9]=3

第一次循环:
  top[7]=1, top[9]=3, 不同
  dep[1]=1 < dep[3]=2, 交换:u=9, v=7
  查询 [dfn[3], dfn[9]] = [5,7](重链3-6-8-9?不对,看具体剖分)
  u = fa[3] = 1

第二次循环:
  top[1]=1, top[7]=1, 相同!
  查询 [dfn[1], dfn[7]] = [1,4]

总共两段,O(log n) 次线段树操作

进阶:LCA(最近公共祖先)

树链剖分可以 O(log n) 求 LCA:

cpp 复制代码
int lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}

进阶:维护边权

将边权转化为点权:每条边的权值赋给子节点

cpp 复制代码
// 边 (u,v,w),假设 v 是 u 的儿子
// 把 w 存在 v 上
w[v] = 边权;

// 查询 u→v 路径上的最大边权(u是v的祖先)
int queryEdge(int u, int v) {  // u是v的祖先
    int res = 0;
    while (top[u] != top[v]) {
        res = max(res, seg.queryMax(dfn[top[v]], dfn[v]));
        v = fa[top[v]];
    }
    // 现在同一条链,u是祖先
    if (u != v)  // 排除u本身(边权在子节点上)
        res = max(res, seg.queryMax(dfn[u]+1, dfn[v]));
    return res;
}

进阶:换根操作

维护以不同节点为根时的子树信息。

cpp 复制代码
// 判断 u 是否是 v 的祖先
bool isAncestor(int u, int v) {
    return dfn[u] <= dfn[v] && dfn[v] < dfn[u] + siz[u];
}

// 换根后,x的子树是什么?
// 设当前根为root,求x的子树
int getRootedSubtree(int root, int x) {
    if (root == x) return 1;  // 整棵树
    if (!isAncestor(x, root)) {
        // x的子树不变
        return seg.query(dfn[x], dfn[x] + siz[x] - 1);
    }
    // root在x的某个儿子的子树中,x的子树是整棵树减去那个儿子的子树
    int y = jump(root, dep[root] - dep[x] - 1);  // 从root往上跳到x的儿子
    return total - seg.query(dfn[y], dfn[y] + siz[y] - 1);
}

// 从u向上跳k步
int jump(int u, int k) {
    while (k) {
        if (dfn[u] - dfn[top[u]] + 1 <= k) {
            k -= dfn[u] - dfn[top[u]] + 1;
            u = fa[top[u]];
        } else {
            return rnk[dfn[u] - k];
        }
    }
    return u;
}

完整封装模板

cpp 复制代码
template<typename T, typename SegTree>
struct TreeChain {
    int n, root;
    vector<vector<int>> g;
    vector<int> fa, dep, siz, son, top, dfn, rnk;
    vector<T> w, wval;
    SegTree seg;
    int tim;
    
    TreeChain(int n = 0, int root = 1) { init(n, root); }
    
    void init(int n_, int root_) {
        n = n_; root = root_;
        g.assign(n + 1, {});
        fa = dep = siz = son = top = dfn = rnk = vector<int>(n + 1);
        w = wval = vector<T>(n + 1);
        tim = 0;
    }
    
    void addEdge(int u, int v) {
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    void setWeight(int u, T val) { w[u] = val; }
    
    void build() {
        dfs1(root, 0);
        dfs2(root, root);
        seg.build(wval, 1, n);
    }
    
    void dfs1(int u, int f) {
        fa[u] = f; dep[u] = dep[f] + 1; siz[u] = 1; son[u] = 0;
        for (int v : g[u]) if (v != f) {
            dfs1(v, u);
            siz[u] += siz[v];
            if (siz[v] > siz[son[u]]) son[u] = v;
        }
    }
    
    void dfs2(int u, int t) {
        top[u] = t; dfn[u] = ++tim; rnk[tim] = u; wval[tim] = w[u];
        if (!son[u]) return;
        dfs2(son[u], t);
        for (int v : g[u]) if (v != fa[u] && v != son[u])
            dfs2(v, v);
    }
    
    // 路径操作
    void pathModify(int u, int v, T val) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            seg.modify(dfn[top[u]], dfn[u], val);
            u = fa[top[u]];
        }
        if (dep[u] > dep[v]) swap(u, v);
        seg.modify(dfn[u], dfn[v], val);
    }
    
    T pathQuery(int u, int v) {
        T res = 0;
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            res = res + seg.query(dfn[top[u]], dfn[u]);
            u = fa[top[u]];
        }
        if (dep[u] > dep[v]) swap(u, v);
        return res + seg.query(dfn[u], dfn[v]);
    }
    
    // 子树操作
    void subtreeModify(int u, T val) {
        seg.modify(dfn[u], dfn[u] + siz[u] - 1, val);
    }
    
    T subtreeQuery(int u) {
        return seg.query(dfn[u], dfn[u] + siz[u] - 1);
    }
    
    int lca(int u, int v) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            u = fa[top[u]];
        }
        return dep[u] < dep[v] ? u : v;
    }
};

时间复杂度分析

操作 时间复杂度 说明
树链剖分(两次DFS) O(n) 预处理
路径修改/查询 O(log² n) 最多 O(log n) 条重链,每条线段树 O(log n)
子树修改/查询 O(log n) DFS序连续,直接线段树
LCA O(log n) 跳重链
换根子树查询 O(log n) 配合额外判断

注意:如果线段树换成 O(1) 查询的数据结构(如ST表),路径查询可优化到 O(log n)。


应用场景总结

应用场景 关键技巧
树上路径修改/查询 拆分为重链区间
子树修改/查询 利用DFS序连续性
边权维护 下放到子节点
换根问题 分类讨论root位置
树上线段树合并 配合DSU on tree
动态树问题 结合Link-Cut Tree

与其他算法的对比

算法 功能 时间复杂度 适用场景
树链剖分 路径/子树操作 O(log² n) 静态树,频繁路径查询
LCT (Link-Cut Tree) 动态树,换根、连边、断边 均摊 O(log n) 动态树结构
欧拉序+线段树 子树操作 O(log n) 只有子树操作,无路径
倍增法 LCA、祖先查询 O(log n) 预处理,O(1) 查询 只需LCA,无需修改

树链剖分是静态树上路径问题的标准解法,代码规整、效率优秀,是竞赛中的必备技能!

相关推荐
寻寻觅觅☆10 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子11 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
化学在逃硬闯CS12 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12312 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS12 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗13 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果13 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮13 小时前
AI 视觉连载4:YUV 的图像表示
算法
ArturiaZ14 小时前
【day24】
c++·算法·图论
大江东去浪淘尽千古风流人物15 小时前
【SLAM】Hydra-Foundations 层次化空间感知:机器人如何像人类一样理解3D环境
深度学习·算法·3d·机器人·概率论·slam