树链剖分
由于是在树上搞的 ds 所以考察数据结构本身性质偏多,需大力注重细节。
思想
考虑将一颗树的链划分成若干个区间进行维护。
这种划分方式叫做剖分。
约束
-
一颗有根树(有时要求换根但不是真正换根)
-
每个点恰好包含在一条剖出的链中(若被多条链同时包含则需要同时维护多条链,修改多余的可能影响复杂度)
-
剖分出来的链上面的节点深度有序
-
每条链的深度最大的端点为叶子节点
重儿子
决定了剖分的方式。
对于一个非叶子节点 \(u\),恰好存在一个儿子 \(v\) 满足 \(u, v\) 在同一条链中,称 \(v\) 为 \(u\) 的重儿子,其他儿子称为轻儿子。
重边与轻边
上述 \((u, v)\) 的边为重边,其余边为轻边。
重儿子种类
下文默认 \(v\) 为 \(u\) 的重儿子。
一般有两种:
-
\(v\) 为 \(u\) 的所有儿子中子树大小最大的
-
\(v\) 为 \(u\) 所有儿子中子树中深度最大的
若多个 \(v\) 满足情况则任取一个。
重链剖分
设 \(u\) 的重儿子为 \(son_u\)。
每条由 \(u \to son_u\) 的链叫做重链。
性质
-
若 \(v\) 是 \(u\) 的轻儿子,那么有 \(sz_u > 2sz_v\)
-
任意节点到根的轻边数量为 \(O(\log n)\)
-
任意两点间的链上轻边数量为 \(O(\log n)\)
-
所有重链链顶节点子树大小之和为 \(O(n \log n)\)
-
重链上的节点 dfs 序连续
维护一段简单路径
由于性质 \(3\),如果我们是维护树上一段路径的信息就比较容易了。
考虑每次拿链顶深度大的跳,由于一段重链的 dfn
是连续的,因此是很容易用数据结构维护的。
至于正确性可以感性理解一下,由于树的特殊结构,你无论怎么往上跳两个点是一定会落到同一条重链上的,并且此过程覆盖了两点路径上的所有点且没有冗余。
下面是伪代码:
while top[x] != top[y]
if dep[top[x]] < dep[top[y]]
swap(x, y);
do something
x = fa[top[x]];
do something