树链剖分

由于是在树上搞的 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

维护子树内信息

鸣谢

https://www.luogu.com.cn/article/e273yi3o

https://www.cnblogs.com/MnZnOIerLzy/p/17812912.html

posted @ 2024-09-09 21:34  end_switch  阅读(6)  评论(0编辑  收藏  举报