一些概念
在学习重链剖分前,首先要明白以下几个概念:
中二重儿子:就是一个节点的儿子中最“重”的那个,“重”表示的是子树大小最大,如果都一样大,就随便选一个就好了(用son数组存储)亲轻儿子:除了重儿子外其他的儿子- 重边:重儿子和父亲之间的边
- 轻边:轻儿子和父亲之间的边
- 重链:重边连在一起形成的链
- 轻链:轻边连在一起形成的链(貌似没啥用)
- 重链顶点:一条重链中,深度最小的点(用top数组记录)
为了方便大家理解,这里我画了一张图,来表示重链剖分后的结果:
其中,红色的线表示重边,连在一起,形成3条重链;黑色的线表示轻边,连在一起,形成3条轻链
这样,大家对这些概念应该都了解了吧?
算法讲解
接下来,我们来介绍如何进行重链剖分
首先,我们进行第一遍dfs,求出子树的大小(siz),重儿子(son),父亲节点(fa)和每个点的深度(dep)
size、fa和dep的求法就不用说了吧……
我们来讲讲son的求法
首先,设一个变量maxson,初值为−1,记录最大的子树大小
接着,对于枚举的每棵子树,假设当前枚举的子树的根为v,判断maxson和siz[v]的大小
如果siz[v]>maxson,那么son[u]=v
代码:
1 | int dfs1(int x,int Fa,int Dep) |
接着,我们来讲一讲第二个dfs,这个dfs要求出dfs序(dfn)和重链顶点(top)
为了方便,我们首先先遍历重儿子,再遍历其他轻儿子,重儿子的重链顶点和该节点的重链顶点相同,轻儿子的重链顶点是自己
至于方便在什么地方,后面再说
代码:
1 | void dfs2(int x,int Top) |
这样,我们就完成了重链剖分的全过程
应用
那么,剖分完后又有什么好处呢?
求最近公共祖先
众所周知,有一种求最近公共祖先(LCA)的算法,使用的是倍增,这个算法预处理是Θ(nlogn)的,单次询问是Θ(logn)的
有比这个更快的算法吗?有!用重链剖分解决!
等等,这和重链剖分有什么关系呢?最近公共祖先和这两个点所在的重链也不一定一样啊,这怎么做呢?
别急,马上大家就会理解了
我们以下面这张图为例,求两个蓝色的点的最近公共祖先
模仿倍增法求LCA,我们就很容易理解重链剖分求LCA的方法了
我们只需要选择深度较深的点,不断向上跳到重链的顶端即可
首先,蓝色的点调到重链顶端(绿点),然后再向上跳一条边(黄点),这样就转换到了另外一条重链上
最后,当两个点在同一条重链上时,深度较小的那个点就是他们的最近公共祖先了
那么,重链剖分求LCA时,预处理为Θ(n),单次询问为Θ(logn)
完整代码:
1 |
|
对树上的一条链进行修改和查询
重链剖分还有什么作用呢?
它可以对树上的任意一条链进行修改
以下图为例,我们假设每个点有一个点权,我们要让两个蓝点之间的链上的每个点的权+1(包括两个蓝点),怎么操作呢?
首先,对于这种相连的东西一起修改的题目,我们很容易就会想到线段树
在树上建线段树,用的一定就是dfs序了
还记得我们前面遍历每个节点的子节点时,是先遍历它的重儿子吗?
当时我说这是为了方便
是的,就是在建线段树和计算时更方便了!
因为所有重链上的点的dfs序一定是连续的,这样,一条重链上的点的值修改只需要进行简单的线段树上区间修改即可
所以我们考虑把链划分成许多个重链相连(如下图)
那么,我们就可以分别修改每一段的值,查询时也一样,分别查询每一段的值,然后再加在一起即可
对于这个链的划分操作,和LCA的求法类似,只需要不断地跳到重链顶点的父亲节点,就可以了
模板题代码:
1 |
|
相关练习题
- 洛谷P4315 月下“毛景树”
- 洛谷P2146 [NOI2015]软件包管理器 题解
- 洛谷P4949 最短距离
- 其他需要用到LCA的题目,都可以使用重链剖分求,这里就不列举了