树链剖分的核心目标是将树上路径拆分为≤log n条重链连续段;通过两次DFS构建dfn、top等数组,使每条重链在DFS序中连续;路径查询时循环跳链,每次取dfn[top[x]]到dfn[x]区间交由线段树处理。

树链剖分的核心目标是把树上路径拆成 ≤ log n 条重链连续段
树链剖分本身不直接“转化”路径为区间,而是通过两次 DFS 构建 dfn(DFS 序)、top、fa、sz 等数组,让每条重链在 dfn 序中占据一段连续区间。路径查询时,用 while (top[u] != top[v]) 循环跳链,每次取 dfn[top[x]] 到 dfn[x] 这一连续区间,交给线段树/树状数组处理。
关键点在于:只有重链内部节点的 dfn 是连续的;轻边连接的链之间 dfn 不连续,必须分段处理。
跳链过程必须按深度大的点先跳,否则区间端点会错乱
常见错误是写成 while (u != v) 然后随意更新 u/v,导致跳过 LCA 或重复覆盖。正确做法是始终让较深的点向上跳到其所在重链顶端,再比较 top:
- 若
dep[u] ,交换 <code>u和v,保证u更深 - 若
top[u] == top[v],说明已在同一重链,直接查询[dfn[v], dfn[u]](注意左小右大)并退出 - 否则查询
[dfn[top[u]], dfn[u]],然后令u = fa[top[u]]
漏掉 dep 比较或写反 dfn 区间左右端点,会导致线段树查到错误位置甚至越界。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
线段树维护的是 dfn 序上的值,不是原树编号
建树时,seg.build(1, 1, n) 的下标范围是 1..n,但第 i 个位置存的是 val[node],其中 node = id[i](id[dfn[x]] == x)。更新或查询都必须用 dfn[x] 作为线段树下标:
// 错误:用原编号 x 当作线段树下标 seg.update(x, new_val); <p>// 正确:用 dfn 序位置 seg.update(dfn[x], new_val);</p>
如果树节点权值初始存在数组 a[] 中,建线段树前要重排成 b[dfn[x]] = a[x],否则数据完全错位。
单点修改和子树查询其实更简单,但容易和路径查询混淆
子树查询只需查 [dfn[x], dfn[x] + sz[x] - 1] —— 因为 DFS 序中子树天然连续;单点修改就是 dfn[x] 处更新。这两者不需要跳链,也不涉及 top 或 fa。
但有人会误把路径查询逻辑套用到子树操作上,比如对子树也写跳链循环,或者查 [dfn[x], dfn[x]] 以为是子树——这是典型混淆了“点”“子树”“路径”三种不同语义。
真正难的不是写跳链,而是想清楚当前操作到底要覆盖哪些节点:LCA 是否包含?方向是否影响区间顺序?有没有边权转点权的偏移?这些细节一旦没对齐,dfn 序再漂亮也没用。

















