树链剖分 笔记

前言

树链剖分用于转化树上的问题,使得它更容易考虑,解决。 主要分两(三)种(虚实剖分会在 LCT 里面讲,剩下的是重链剖分和长链剖分)

公共套路

对于每个节点,钦定 一个儿子当“重儿子”,然后每个点和重儿子形成的链叫“重链”,其它的是“轻链”。然后树会变成若干条直链拼一起,于是把树上的路径问题变成了序列上的问题。

重链剖分

重链剖分用于在 \(O(\log n)\) 的时间里拆分任意的链。它适合套个线段树,主席树,线性基,堆等能合并的数据结构,来解决树上的路径问题。

它选重儿子的方法是:\(size\) 最大的是重儿子。“容易证明”,这样的话,每一条路径都顶多会被拆成 \(O(\log n)\) 条链。

证明:

不是我口胡,真的好证。对于每条路径 \((u,v)\),我们把它拆分成 \((u,LCA)+(LCA,v)\)。然后现在就变成了一条直链,证明它最多会被划分成 \(\log\) 条重链。继续观察,我们发现:一条链被划分成的重链数=它经过的轻边数+1(因为轻边是划分重链的)。那我们只要考虑它经过多少轻边。

对于一个点 \(u\),我们走一条轻边到 \(v\),它的 \(size\) 至少会除以 \(2\)。反证:如果 \(size[v]>size[u]/2\),那么显然 \(v\) 是重儿子,然而我们设 \((u,v)\) 是轻边,矛盾。所以这个结论成立。

那么一条链经过的轻边数就是 \(O(\log n)\) 的。两条加一起就是多一个常数,还是 \(\log\) 的。

然后就非常的 simple 了,只要序列上能做的问题,就能多一个 log 的时间放到树上。

当然,有些问题要注意方向。比如求路径上矩阵的乘积,这种记得多讨论亿点细节。

还有,在一个不是刻意构造的树上,一般来讲重链剖分常数是很小的。那就可以考虑重链剖分求 LCA。尽管代码比倍增长不少,但是它很快。

长链剖分

长链剖分用来不带 \(\log\) 的解决只跟深度有关的问题。

可以想象一下,它要是也是 \(\log\),那还不如写重链剖分;它既然被发明出来,那它一定有它独一无二之处。它就是那种,在一个小问题上做到极致的算法。

不废话了,讲。

和重链剖分只有一个不同,就是它定义重儿子为最 "深" 的儿子。最“深”的儿子,形式化的,就是点 \(v\in son(u)\),使得它的 \(d_{max}\) 最大,其中 \(d_{max}(u)=max\{dep[v] | v\in subtree(u)\}\)

在讲到长链剖分的时候,一般管重链叫“长链”。

长链剖分基本性质:如果 \(x\)\(y\)\(k\) 级祖先,那么 \(x\) 所在的长链长度肯定 \(>=k\)

这个性质yanQval直呼显然,因为如果 \(x\) 所在的长链长度 \(<k\),那把它换成 \(<x,y>\) 这条链,就会更长,与长链的定义矛盾。

然后看它能干啥。

  1. \(O(1)\)\(k\) 级祖先(预处理有 \(\log\),查询没有)

    首先要预处理倍增数组。

    然后还要预处理一个东西:对于每条重链的链顶 \(t\),假设链长度为 \(d\),预处理出 \(t\) 往上往下 \(d\) 个位置,用 vector<> 存。显然,所有链的长度加起来是 \(n\)。所以这一步的占用空间是 \(2n\)。不会炸。

    然后还要预处理出 \(\log_2\) 数组(也就是二进制最高位)

    然后开始查询。

    先找到最大的 \(x\) 使得 \(x<=k\) 并且 \(x\) 是二的幂(查 \(\log_2\) 数组)。用倍增数组跳上去。

    设跳到了 \(x'\)。根据基本性质,\(x'\) 所在的长链长度肯定 \(>=x\)。由于 \(x\) 是最大的二次幂,所以 \(x>k-x\)。于是 \(x'\) 所在的长链长度肯定 \(>k-x\)。然后查表就行了。

  2. 线性存储每个点跟深度有关的数组

    做树上问题会经常遇到一个数组:\(f_{u,i}\) 表示 \(u\) 的子树中和 \(u\) 距离 \(i\) 的 xxxx。

    然后考虑把所有的 \(f\) 存在一个内存池里,然后 \(f_u\) 可以直接继承其重儿子,对于轻儿子,就更新一下就好了。

    关于如何继承:设 \(u\) 的重儿子是 \(s\),那么 \(f_{u,i}=f_{s,i-1}\)

    如果 \(f_u\) 在内存池中的指针是 \(p\),直接令 \(f_{s}=p+1\) 即可。

    然后对于 \(u\) 的每个轻儿子 \(v\)\(f_{u,i}+=f_{v,i-1}\)

    显然这里 \(i\) 枚举的范围是:轻儿子所在的重链长度

    所有重链长度和为 \(n\)。所以这样做复杂度是 \(O(n)\) 的。然而实际存储似乎需要四倍常数。不知道咋整的。

虚实剖分

它是一个动态钦点重边的剖分。

先不看下去,就看“动态”二字,想象一下它在干什么。然后就可以去看 LCT 一章了!

posted @ 2021-05-04 23:06  Flandre-Zhu  阅读(56)  评论(0编辑  收藏  举报