当前位置: 首页 > news >正文

【板子】LCA 树链剖分

这是另一种非常经典的求解最近公共祖先(LCA)的方法:树链剖分(Heavy-Light Decomposition)

与Tarjan 算法(离线算法)不同,树链剖分是一种在线算法

1. 核心概念:什么是“重”和“轻”?

树链剖分的核心思想是将一棵树切分成若干条链,使得在查找路径时效率最高。为了实现这一点,它定义了以下概念:

  • 重儿子 (Heavy Son):对于节点 u,它的所有子节点中,子树节点数量最多的那个儿子。

  • 轻儿子 (Light Son):除了重儿子以外的所有儿子。

  • 重边 (Heavy Edge):连接节点 u 和它的重儿子的边。

  • 轻边 (Light Edge):连接节点 u 和它的轻儿子的边。

  • 重链 (Heavy Chain):由多条重边连接而成的路径。

性质:任意一条树上的路径,最多只会被切成 logn 条链。这就是为什么它速度快的原因。

2. 需要维护的数组

为了实现树链剖分,我们需要维护以下几个关键数组:

  • fa[u]:节点 u 的父节点。

  • dep[u]:节点 u 的深度(根节点深度通常为 1)。

  • sz[u]:以 u 为根的子树的节点总数。

  • son[u]:节点 u 的重儿子(如果没有则为 0)。

  • top[u]:节点 u 所在重链的顶端节点。

3. 算法流程与代码模板

树链剖分求 LCA 的过程分为两个阶段:两次 DFS 预处理​ 和在线查询

第一阶段:预处理(两遍 DFS)

第一遍 DFS (dfs1) 负责计算子树大小、父节点、深度和重儿子。

第二遍 DFS (dfs2) 负责给节点分配链顶(top),将树真正剖分成链。

#include <iostream> #include <vector> #include <cstring> using namespace std; const int N = 500010; vector<int> e[N]; // 邻接表存图 // 树链剖分核心数组 int fa[N], dep[N], son[N], sz[N]; int top[N]; // 链顶数组 // 第一遍 DFS:找重儿子、算大小、算深度 void dfs1(int u, int father) { fa[u] = father; dep[u] = dep[father] + 1; sz[u] = 1; son[u] = 0; // 初始化没有重儿子 for (auto v : e[u]) { if (v == father) continue; dfs1(v, u); sz[u] += sz[v]; // 累加子树大小 // 更新重儿子:如果当前儿子v的子树比之前记录的重儿子还大,就更新 if (sz[v] > sz[son[u]]) { son[u] = v; } } } // 第二遍 DFS:连重链、标记链顶 void dfs2(int u, int t) { top[u] = t; // 记录当前点所在的链顶 if (son[u] == 0) return; // 如果没有重儿子,说明到底了 // 1. 优先递归处理重儿子,重儿子的链顶和当前点一样 dfs2(son[u], t); // 2. 处理轻儿子,轻儿子开启一条新的链 for (auto v : e[u]) { if (v == fa[u] || v == son[u]) continue; dfs2(v, v); // 新的链,链顶就是自己 } } // 核心查询函数:求 u 和 v 的 LCA int lca(int u, int v) { // 核心思想:当两个点不在同一条重链上时,让深度较大的那个点跳到链顶的父亲 while (top[u] != top[v]) { // 优化:总是让深的点往上跳,减少代码行数 if (dep[top[u]] < dep[top[v]]) swap(u, v); // 把 u 跳到链顶的父节点 u = fa[top[u]]; } // 跳出循环时,说明 u 和 v 在同一条重链上了 // 此时深度较小的那个点就是 LCA return dep[u] < dep[v] ? u : v; } int main() { int n; // 节点数 cin >> n; // 读入 n-1 条边建图 for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; e[a].push_back(b); e[b].push_back(a); } // 初始化根节点信息并开始剖分 dfs1(1, 0); // 假设根为 1 dfs2(1, 1); // 处理查询 int q; // 查询次数 cin >> q; while (q--) { int u, v; cin >> u >> v; cout << lca(u, v) << endl; } return 0; }

4. 原理解释(如何求出 LCA?)

lca函数的逻辑利用了“重链”的性质,可以把它想象成在树上“走楼梯”:

  1. 不在同一条链上(top[u] != top[v]

    • 如果 u 和 v 不在同一条重链上,说明它们之间有垂直的距离。

    • 我们总是让当前位置比较“深”(dep大)的那个点,沿着它所在的重链一直往上爬,直到到达链顶(top)。

    • 然后,再从链顶跳到链顶的父节点(u = fa[top[u]])。这就相当于跨过了这条重链,进入了另一条链。

    • 为什么要从轻儿子开始开新链?​ 因为轻儿子的子树大小至少减半,所以每经过一条轻边,子树规模至少减少一半。这保证了从任意节点到根节点的路径上,最多只有 logn 条轻边,从而保证了跳跃次数是 logn 级别的。

  2. 在同一条链上(top[u] == top[v]

    • 当循环结束,说明 u 和 v 终于落在了同一条重链上。

    • 因为它们在同一条直线上,所以位置靠下的那个点(深度小的)必然是另一个点的祖先。

    • 直接返回dep[u] < dep[v] ? u : v即可。

如图:

假设查询11,9

11会沿着自己重链上升,到4时,9显然更深,9已经是连顶,跳到父节点,4;此时处于同一个链,4为答案。

http://www.jsqmd.com/news/1279971/

相关文章:

  • 专业的高低温试验箱品牌!海孚威测 - GrowUME
  • 基于ESP8266与人体感应实现Wi-Fi触发自动视频播放系统
  • 构建离线编程教学体系:从Scratch依赖到计算思维培养
  • 贺州黄金回收实测:万金汇5店覆盖全城,附避坑技巧 - 观金堂黄金回收
  • 2026年,苏州宁飞龙律师分享刑事辩护那些不得不说的事 - 资讯快报
  • 辽宁校园招聘机构 解决校招匹配难题 合规机构推荐 - 资讯快报
  • 2026雨花区大宅木作厂家哪家好?大宅木作厂家推荐选购指南+避坑攻略 - mobible
  • 华为OD机试矩阵最大值:从基础遍历到流式处理的算法内功
  • 7.26dfs周测复盘
  • Nomad 配置指南:打造个性化的 Neovim 协作环境
  • NBM5100A电池增强器在物联网设备中的高效应用
  • 2026杭州及周边系统门窗怎么选?别只看隔音效果,先看工艺、工厂直营和交付边界 - 中国品牌价值观察网
  • 装修网上接单怎么接?2026 四大主流网单渠道对比,按需选择更容易签单 - 家居行业测评
  • 终极指南:如何通过内存补丁技术免费解锁WeMod Pro完整功能
  • 2026 压缩图片大小的软件工具怎么选,从功能到速度逐一测试 - 软件工具教程方法
  • 2026长治市卖黄金别踩坑!全域五家靠谱门店实测评级,这份避坑指南请收好_转自TXT - 余情未了888
  • Arduino激光打靶装置:光电传感与伺服控制的嵌入式互动项目实践
  • 2026南昌财务公司盘点:四家本地服务商值得一看 - 商讯
  • AI搜索的答案生成机制及对内容运营的影响
  • 麦克纳姆轮机器人运动控制:从运动学原理到工程实践
  • OneDragon:绝区零自动化框架核心技术架构深度解析
  • 2026年重庆靠谱软件系统公司盘点及选择指南
  • 虚拟会议系统架构演进与智能优化实践
  • 二、 Linux 基础入门
  • 户口本翻译去哪里办理?正规户口本翻译办理流程是什么?高效出件! - 叮咚办真方便
  • 2026东莞爱马仕回收避坑攻略|正规无损估价高价变现选易奢福 - 回收奢侈品探店测评
  • 一生一芯学习记录(一):简单介绍 + 建立Verilator仿真环境
  • 2026招远市卖黄金别踩坑!全域五家靠谱门店实测评级,这份避坑指南请收好_转自TXT - 余情未了888
  • 宁波黄金回收实测:多家正规门店大盘盘点,附避坑指南 - 好物测评局
  • AI驱动测试平台Mabl实战:自维护E2E与视觉回归测试深度解析