长链剖分(Long Chain Decomposition)算法详解

1. 什么是长链剖分

长链剖分(Long Chain Decomposition)是一种针对有根树的链剖分方法,常用于解决树上与深度、距离相关的静态查询问题。与重链剖分(Heavy-Light Decomposition)不同,长链剖分优先选择子树中最深的儿子作为"长儿子",从而将树分解为若干条"长链"。

长链剖分的核心思想是:对于每个节点,选择其子树深度最大的儿子作为"长儿子",然后将该节点与其长儿子连接成同一条链。这样,整棵树就被分解为若干条从某个节点开始,一直沿着长儿子向下延伸的链,这些链被称为"长链"。

2. 长链剖分的构建

长链剖分的构建可以通过一次深度优先搜索(DFS)完成,时间复杂度为 O(n)。

算法步骤:

  1. 第一次 DFS:计算每个节点的深度(depth)和子树最大深度(max_depth)。
  2. 第二次 DFS:为每个节点确定"长儿子"(即子树最大深度最大的儿子)。
  3. 从根节点开始,将每个节点与其长儿子连接,形成长链。

3. 代码实现(C++)

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

const int N = 1e5 + 5;
vector<int> g[N];  // 邻接表存树
int depth[N], max_depth[N];  // 深度、子树最大深度
int son[N];  // 长儿子
int top[N];  // 所在长链的顶端节点

// 第一次 DFS:计算深度和子树最大深度
void dfs1(int u, int fa) {
    depth[u] = depth[fa] + 1;
    max_depth[u] = depth[u];
    for (int v : g[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        max_depth[u] = max(max_depth[u], max_depth[v]);
        if (max_depth[v] > max_depth[son[u]]) {
            son[u] = v;  // 更新长儿子
        }
    }
}

// 第二次 DFS:构建长链
void dfs2(int u, int fa, int tp) {
    top[u] = tp;
    if (son[u]) {
        dfs2(son[u], u, tp);  // 长儿子继承当前链
    }
    for (int v : g[u]) {
        if (v == fa || v == son[u]) continue;
        dfs2(v, u, v);  // 其他儿子作为新链的顶端
    }
}

int main() {
    int n;  // 节点数
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1, 0);
    dfs2(1, 0, 1);
    // 输出每个节点所在长链的顶端
    for (int i = 1; i <= n; i++) {
        cout << "节点 " << i << " 所在长链顶端: " << top[i] << endl;
    }
    return 0;
}

4. 长链剖分的性质与应用

4.1 主要性质

  • 链长性质:每条长链的长度等于链顶端节点的子树最大深度减去该节点的深度。
  • 链数上界:长链的数量不超过 O(√n),实际应用中通常远小于这个上界。
  • 深度性质:任意节点到其所在长链顶端的距离不超过该节点子树的最大深度。

4.2 典型应用

  1. 树上 k 级祖先查询:预处理 O(n log n),查询 O(1)。
  2. 树上两点距离查询:结合 LCA,可以快速计算任意两点距离。
  3. 子树深度相关统计:如子树中深度为 d 的节点数量。
  4. 优化树上 DP:通过指针继承技巧,将某些树形 DP 的复杂度从 O(n²) 降为 O(n)。

5. 长链剖分 vs 重链剖分

对比维度 长链剖分 重链剖分
选择标准 子树深度最大的儿子 子树大小最大的儿子
链长特点 链较长,数量较少 链较短,数量较多
主要应用 深度、距离相关查询 路径修改、子树查询
复杂度 通常 O(n) O(n log n)
代码难度 相对简单 相对复杂

6. 实战例题

6.1 例题:树上 k 级祖先

问题描述:给定一棵 n 个节点的有根树,有 q 次查询,每次查询给出节点 u 和整数 k,求 u 的第 k 级祖先(如果不存在则输出 -1)。

数据范围:n, q ≤ 10⁵。

长链剖分解法思路

  1. 预处理每个节点的 2^i 级祖先(倍增)。
  2. 对每条长链,预处理从链顶向上/向下走链长步的所有节点。
  3. 查询时,先利用倍增跳到 2^h 级祖先,使得剩余步数小于链长,然后通过预处理的链信息 O(1) 得到答案。

7. 总结

长链剖分是一种高效的树上问题处理技巧,特别适合解决与深度、距离相关的静态查询问题。通过优先选择深度最大的儿子,将树分解为较少的长链,从而在预处理和查询时获得优异的时间复杂度。

掌握长链剖分需要理解其构建过程、核心性质以及指针继承等优化技巧。建议通过实际编码练习来加深理解,特别是树上 k 级祖先、深度统计等经典问题。

相关推荐
qeen876 小时前
【数据结构】自平衡二叉搜索树各种旋转算法原理解析及AVL树的C++实现
数据结构·c++·算法
lucas_AI6 小时前
1.2B 小模型赢过 235B 大模型:NaviDC-OCR 把文档解析卷明白了
人工智能·深度学习·算法
冻柠檬飞冰走茶7 小时前
PTA基础编程题目集 7-35有理数均值(C++语言实现)
开发语言·数据结构·c++·算法·均值算法
民乐团扒谱机7 小时前
【微实验】倒谱算法(Cepstrum)深度解析:原理、数学推导与代码实现
人工智能·算法·语音识别
Nil2088 小时前
leetcode 189轮转数组
数据结构·算法·leetcode
-dzk-8 小时前
【动态规划】LC 118.杨辉三角
算法·动态规划
恣逍信点8 小时前
《凌微经》静态自悖——变化之自因
人工智能·科技·算法·机器学习·业界资讯·拓扑学·哲学
吃着火锅x唱着歌9 小时前
LeetCode 3885.设计事件管理器
算法·leetcode·职场和发展
土司大王9 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode
Eloudy9 小时前
预词力:LLM 的唯一形式化能力
人工智能·算法·agent