树链剖分 笔记
前言
树链剖分用于转化树上的问题,使得它更容易考虑,解决。 主要分两(三)种(虚实剖分会在 LCT 里面讲,剩下的是重链剖分和长链剖分)
公共套路
对于每个节点,钦定 一个儿子当“重儿子”,然后每个点和重儿子形成的链叫“重链”,其它的是“轻链”。然后树会变成若干条直链拼一起,于是把树上的路径问题变成了序列上的问题。
重链剖分
重链剖分用于在 的时间里拆分任意的链。它适合套个线段树,主席树,线性基,堆等能合并的数据结构,来解决树上的路径问题。
它选重儿子的方法是: 最大的是重儿子。“容易证明”,这样的话,每一条路径都顶多会被拆成 条链。
证明:
不是我口胡,真的好证。对于每条路径 ,我们把它拆分成 。然后现在就变成了一条直链,证明它最多会被划分成 条重链。继续观察,我们发现:一条链被划分成的重链数=它经过的轻边数+1(因为轻边是划分重链的)。那我们只要考虑它经过多少轻边。
对于一个点 ,我们走一条轻边到 ,它的 至少会除以 。反证:如果 ,那么显然 是重儿子,然而我们设 是轻边,矛盾。所以这个结论成立。
那么一条链经过的轻边数就是 的。两条加一起就是多一个常数,还是 的。
然后就非常的 simple 了,只要序列上能做的问题,就能多一个 log 的时间放到树上。
当然,有些问题要注意方向。比如求路径上矩阵的乘积,这种记得多讨论亿点细节。
还有,在一个不是刻意构造的树上,一般来讲重链剖分常数是很小的。那就可以考虑重链剖分求 LCA。尽管代码比倍增长不少,但是它很快。
长链剖分
长链剖分用来不带 的解决只跟深度有关的问题。
可以想象一下,它要是也是 ,那还不如写重链剖分;它既然被发明出来,那它一定有它独一无二之处。它就是那种,在一个小问题上做到极致的算法。
不废话了,讲。
和重链剖分只有一个不同,就是它定义重儿子为最 "深" 的儿子。最“深”的儿子,形式化的,就是点 ,使得它的 最大,其中 。
在讲到长链剖分的时候,一般管重链叫“长链”。
长链剖分基本性质:如果 是 的 级祖先,那么 所在的长链长度肯定 。
这个性质yanQval直呼显然,因为如果 所在的长链长度 ,那把它换成 这条链,就会更长,与长链的定义矛盾。
然后看它能干啥。
-
求 级祖先(预处理有 ,查询没有)
首先要预处理倍增数组。
然后还要预处理一个东西:对于每条重链的链顶 ,假设链长度为 ,预处理出 往上往下 个位置,用
vector<>
存。显然,所有链的长度加起来是 。所以这一步的占用空间是 。不会炸。然后还要预处理出 数组(也就是二进制最高位)
然后开始查询。
先找到最大的 使得 并且 是二的幂(查 数组)。用倍增数组跳上去。
设跳到了 。根据基本性质, 所在的长链长度肯定 。由于 是最大的二次幂,所以 。于是 所在的长链长度肯定 。然后查表就行了。
-
线性存储每个点跟深度有关的数组
做树上问题会经常遇到一个数组: 表示 的子树中和 距离 的 xxxx。
然后考虑把所有的 存在一个内存池里,然后 可以直接继承其重儿子,对于轻儿子,就更新一下就好了。
关于如何继承:设 的重儿子是 ,那么 。
如果 在内存池中的指针是 ,直接令 即可。
然后对于 的每个轻儿子 ,
显然这里 枚举的范围是:轻儿子所在的重链长度
所有重链长度和为 。所以这样做复杂度是 的。然而实际存储似乎需要四倍常数。不知道咋整的。
虚实剖分
它是一个动态钦点重边的剖分。
先不看下去,就看“动态”二字,想象一下它在干什么。然后就可以去看 LCT 一章了!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】