树链剖分 45 分钟从零到实战:路径查询与换根操作深度拆解
树链剖分 45 分钟从零到实战路径查询与换根操作深度拆解【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki在一棵 10^5 个节点的树上处理 10^5 次「路径权值和」查询暴力枚举要 10^10 量级的运算必然超时。OI-wiki 给出的答案是重链剖分方案把树切成若干条链每条链交给一棵线段树维护单次路径查询压到 O(log²n)——这就是树链剖分的核心思想。技术画像树链剖分解决什么问题从复杂度分析和族谱位置给树链剖分快速定位解决的问题静态树上的路径查询与修改、子树查询与修改、LCA最近公共祖先两个节点所有公共祖先里最深的那个复杂度量级预处理 O(n)单次路径操作 O(log²n)一个 log n 来自重链段数一个 log n 来自线段树族谱位置属于「树拍平」一族和 DFS 序、欧拉序配合的思路一样本质是把树压成一维序列上的区间与倍增法的一句话差异倍增法只能沿祖先逐位跳擅长单点 LCA树链剖分把整条路径切成连续区间能做求和、取最值这类可合并信息与长链剖分的一句话差异长链剖分按「深度最大的儿子」剖服务 DP 优化重链剖分按「子树最大的儿子」剖服务区间维护机制拆解重链剖分如何组织数据与执行跳链轻重边与重链数据是怎么被组织的直观理解一下把树想象成公路网最宽的干道是重边干道首尾相接成的主干道就是重链。精确定义子树规模最大的那个儿子叫重儿子父亲与重儿子的边是重边到其余儿子的边是轻边若干条首尾衔接的重边构成一条重链落单节点也算一条链。图一棵树的树链剖分示意深色节点为重儿子粗线为重边绿色方框圈出的整段即一条重链关键性质是链数的上界沿任意路径往下走每穿过一条轻边所在子树大小至少减半所以从根到任意节点最多经过 log₂n 条轻边n 10^5 时约 17 条。由此任意一条简单路径最多被切成 O(log n) 段重链这是后面所有复杂度分析的基石。两次 DFS 如何完成剖分第一次 DFS 是常规后序统计算出每个节点的父节点 fa、深度 dep、子树大小 siz并顺手挑出重儿子 son。第二次 DFS 负责「拍平」给每个节点分配 DFS 序 dfn节点在一维序列中的编号同时记录它所在链的链顶 top链上深度最小的节点。DFS 序有两段连续性直接决定后续所有操作的写法重边优先遍历保证同一条重链的 dfn 连续普通 DFS 的性质保证同一棵子树的 dfn 连续。如果跳过第二次 DFS 的重边优先顺序重链会被打散成不连续的多段线段树直接失效。从零构建40 行 C 最小实现下面给出树链剖分 C 实现的三个核心函数共 40 余行配一个标准线段树即可嵌入任意题解。第一次 DFS统计子树大小并选定重儿子dfs1 递归整棵树边回溯边累加子树大小用「子树更大」这条判据就地选出重儿子。void dfs1(int u, int f) { fa[u] f; dep[u] dep[f] 1; siz[u] 1; for (auto v : G[u]) { if (v f) continue; dfs1(v, u); siz[u] siz[v]; if (siz[v] siz[son[u]]) son[u] v; } }第二次 DFS重边优先地分配 DFS 序dfs2 的全部关键在递归顺序先递归重儿子链顶参数不变留在同一条链再逐个递归轻儿子它自己顶替成为新链的链顶。void dfs2(int u, int ftop) { top[u] ftop; dfn[u] idx; rnk[idx] u; if (son[u]) dfs2(son[u], ftop); for (auto v : G[u]) if (v ! son[u] v ! fa[u]) dfs2(v, v); }rnk 是 dfn 的逆映射后面把区间端点换回节点编号时会用到。至此 n 个节点全部落在线段树 1 到 n 的位置上建树部分与任何标准模板无异。路径查询的链顶跳跃逻辑跳链规则两点不在同一条链时比较两个链顶的深度把链顶更深的一侧从链顶到当前节点整段查掉然后把游标跳到链顶的父亲——这一步是关键因为链顶的父亲所在链才是路径的下一段。int query_path(int u, int v) { int res 0; while (top[u] ! top[v]) { if (dep[top[u]] dep[top[v]]) swap(u, v); 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]); }循环最多跑 O(log n) 轮每轮一次 O(log n) 的线段树查询。把 query 换成 add、把和换成 max区间修改与路径最值就都覆盖了。场景实战路径、子树与换根三类操作各练一遍三个典型场景分别覆盖路径查询、子树修改和换根操作能力维度各不相同。LCA跳链到同一条重链问题只查 LCA不维护任何权值。需要线段树吗不需要——只借用跳链过程本身。谁的链顶深谁先跳跳到同一条链后深度较小的节点即答案int lca(int u, int v) { while (top[u] ! top[v]) { if (dep[top[u]] dep[top[v]]) u fa[top[u]]; else v fa[top[v]]; } return dep[u] dep[v] ? u : v; }单次 O(log n) 且常数极小可以对照 docs/graph/lca.md 里的倍增法理解两者差异。子树修改用 DFS 序打区间问题把以 u 为根的子树所有节点权值加 w。子树的 dfn 恰好是 [dfn[u], dfn[u]siz[u]-1] 这段连续区间一次区间加就完成void update_subtree(int u, int w) { seg.add(dfn[u], dfn[u] siz[u] - 1, w); }注意这一步本身不依赖重链信息普通 DFS 序就够了但 siz 必须来自 dfs1混用两套序会直接出错。换根操作把当前树子树映射回原始树问题LOJ 139 要求支持任意换根之后的路径与子树操作。树剖信息是静态的不可能每次重跑预处理正确做法是所有操作都落在以 1 为根的原始树上子树操作按 u 与新根 root 的相对位置分三种情况。路径操作与换根无关照常跳链子树操作的核心代码void subtree_add(int u, int w) { if (u root) return seg.add(1, n, w); int r root; while (dep[top[r]] dep[u] 1) r fa[top[r]]; if (is_ancestor(u, r)) { int v rnk[dfn[top[r]] dep[u] 1 - dep[top[r]]]; seg.add(1, dfn[v] - 1, w); seg.add(dfn[v] siz[v], n, w); } else seg.add(dfn[u], dfn[u] siz[u] - 1, w); }图子树操作映射到 DFS 序区间时的划分方式v 子树之外的两段恰好构成补集u 是新根祖先时当前树里 u 的子树等于整棵树挖掉 v 的子树v 是 u 到 root 路径上除 u 外深度最小的节点。先沿重链把 r 往上提到 u 的下一层再用那条 rnk 表达式精确取到 v两次区间加就是补集。踩坑与边界实战里最容易翻车的细节复杂度分析看着安全这几处边界才是真正翻车的地方⚠️ 树退化成一条链时DFS 递归深度就是 nn 10^5 以上建议改迭代写法不然爆栈跳链后 u fa[top[u]] 可能变成 0链顶是根dep、top 等数组的 0 号位是脏数据写 is_ancestor 之类的辅助函数时别拿 0 号节点取属性路径修改的区间是闭区间 [dfn[top[u]], dfn[u]]链顶节点本身必须包含在内端点漏算是最常见的 WA 来源模板题样例一错一个准子树右端点写 dfn[u] siz[u] - 1半开区间版本 [dfn[u], dfn[u]siz[u]) 两种都行但全篇必须统一混用必错别想当然估常数规整的完全二叉树跳链轮数远达不到 log n 上界常数很小反过来链状树只有 1 条重链直接退化成单次 O(log n) 的线段树操作延伸路径树链剖分往哪走按学习成本排序三个方向长链剖分改按最深儿子剖分专门优化带深度维的树形 DP把 O(n²) 压到 O(n) 级与重链剖分对照着看最直观LCTLink-Cut Tree动态加边删边的场景下静态重链不再够用LCT 用 splay 实现在线维护重链是下一步的自然进阶非传统应用树链剖分不只是区间工具配合距离询问还能逐步还原一棵未知树的结构思路反直觉但很巧妙仓库内可直接对照 docs/graph/hld.md完整推导与全部例题和 docs/graph/code/hld/hld_1.cppZJOI2008 树的统计参考实现。练习由易到难洛谷 P3379树剖求 LCA无需数据结构、洛谷 P3384重链剖分模板题、NOI2015 软件包管理器子树加开关状态边界处理量大。把树压成区间之后你手里就多了一把通吃树路径问题的钥匙。【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考