简介
Link Cut Tree简称LCT,是维护动态树方式的一种,是一个可以对树进行添加链或子树,删除链或子树等等,可以支持对树的结构进行修改的算法。
与树链剖分的区别
树链剖分只能维护静态树,就是只能对树上的点的值进行修改的算法,一般还是用线段树来维护的。
所以LCT就厉害了,首先是维护方式不同,其次它是用splay来维护的。树链剖分可以干的,LCT都可以干,但LCT可以干的,树链剖分有些并不能干。而且LCT短啊!
学习LCT,必须要有splay的基础。如果还不会splay,请看splay学习小记
一些概念
首先要知道一些概念。
现在有一棵树,它需要维护,我们称其为原树。
splay本身其实一个树形结构,我们称其为辅助树。
我们用辅助树来维护原树,注意这是两颗不同的树。
类似树链剖分维护重链,LCT需要维护的是偏爱路径
刚开始我学习LCT看网上的资料时,一直看不懂,偏爱路径到底是什么。经历了好长的一番摸索。
我们首先来讲什么是偏爱儿子。偏爱儿子就是你最后访问到的节点。比如说图1access之前的树(先不要管access是什么),我们先不断的加点进去,比如说加入了一个点O,那么这个点就是偏爱节点。简单的来说最后访问到的点就是当前你要对这个点进行操作,那么这个点就是偏爱节点。(如果还不是很清楚,讲到access就清楚了)。
偏爱节点到根的路径就是偏爱路径。
偏爱路径上的边叫做偏爱边。
节点x上第一条边不为偏爱边的连接点叫做Path Parent。比如说图1中access之前的树,L的Path Parent就是I。用pfa数组来表示。
splay的维护方式
splay中每个x有两个子节点,t[x][0]和t[x][1]。
序列中的splay是左儿子的序号比x小,右儿子的序号大于x。所以在树上的情况类似:左儿子在树中的深度小于x,右儿子在树中的深度大于x。有deep[x]表示节点的深度,在树上splay维护的条件就是deep[t[x][0]]≤deep[x]≤deep[t[x][1]]。
功能
将根节点到x的路径变为偏爱路径
这个过程名叫access,是LCT中的核心。如图1,access(N)之后,A到N的路径就变成了偏爱路径。
但是,为什么其他路径上还保留着偏爱边呢,不是含有最后访问的节点的路径才是偏爱路径吗?
其实LCT为了方便维护,更新偏爱路径并不会把原来所有的偏爱路径删去,只会修改原来偏爱路径最与Path Parent相连接的那一条边而已。并不是只有最后一个节点的那一条路径有偏爱边,只是那一条到的路径全都是偏爱边及偏爱路径(仔细地研究图一的后面那张图,因为辅助树只要deep[t[x][0]]≤deep[x]≤deep[t[x][1]]就可以了,所以与Path Parent相连接的顶点不一定是深度最小的节点)。
一般只有对某个节点x进行操作就需要打access(x)。
这里的lca有什么用后面再说。
access的思路就是:跟着pfa一直往上跳,一直跳到pfa为0且这个节点是当前辅助树的根节点就停止。其实就是将偏爱路径一直向上延伸,知道无法延伸为止。所以建立根节点到x节点的偏爱路径结束之后,根节点并不一定是原来的根节点,就是根节点可能会变换。假设当前的root=u,access(x)之后,root可能变为了v,这一点很重要,会关系到后面操作的理解。
access需要好好的理解一番……
我们来分析一下,splay(x,0)就是把x旋转为它所在的这颗树上的根节点(这就体现了把原来偏爱边保留的优势了)。因为有splay,所以这颗树的root会变。
然后就要把当前x与右儿子的连边断开,因为access(x)是让1到x的路径为偏爱路径,x的子树上的边都不在偏爱路径中。因为t[x][1]的深度比x大,所以把连边删掉。pfa[t[x][1]]=x,因为现在x现在与是t[x][1]断开了连接,那么pfa[t[x][1]]=x,x与t[x][1]之间的连边成为t[x][1]上第一条不为偏爱边的边。
然后把x与y进行连边,这个y初始为0,看看最后一行 y=x,x=pfa[x],所以之后y会变为x,x会变成pfa[x],那么这里的连边就是把x和pfa[x]进行连边。
然后把pfa[y]清零,可见这有当x为这颗偏爱边组成的子树(这颗子树的偏爱边可以为0)的根节点时才会有Path Parent。如图1的后面那张图,只有细边下面的点才会与Path Parent,其他点(包括根节点)的Path Parent都为0。
然后在更新x的值就好了。
换根操作
把x变为当前这棵树的根节点(对于LCT来说是可以维护森林的,所以可能有很多棵树)。
bz[x]^=1是什么意思呢?
因为换了根之后,原来的根节点到x的深度都会进行翻转。所以要打一个标记。
但是翻转之后不会影响别的节点吗?
不会,因为access(x)之后,对根节点到x的路径上的点操作,只有根节点到x的路径上的点会被影响到。
旋转
把x旋转为y的子节点,splay的基本操作。
不过这里rotate要注意一个问题,因为旋转时会影响pfa,所以要更新pfa。
不会详见splay学习小记
标记下传
这里只有翻转的标记下传,具体的据题目的不同而不同。不会详见splay学习小记
更新节点的值
据题目的不同而不同。
连接两颗子树
一个节点也把他当做一个子树。
这里的x和y是分先后的,y深度大于要为x的深度父亲。不过假如题目并没有深度限制,比如说是一个图的建边,那么x,y就不分先后,比如说在魔法森林这题。
x一定要为当前那棵树的根节点才能连接,否则可能原本就有父亲,一连就错了。
断开两个节点的连边
先让x变为根节点(那么x的深度就比y小了)才方便将x与y放入同一棵子树中。然后将y旋转为这棵子树的根节点,因为x深度比y小,那么x肯定在y的左边,断开连边就好了。注意要把pfa清空。
找到原来的父亲
与splay中找左右节点的原理一样。
对树中x节点到y节点进行操作
例如求极值。
方法1<推荐>
像删除一样,首先把x到y都放入同一棵子树中,因为x可能不是根节点,所以需要把其中一个旋转为根节点,我选择y(也可以选x),然后在得出答案。
方法2
现在就用到access中的lca了,这个方法比较机智。因为在access(x)之后,access(y)中,最后一次的pfa[x]=0的x一定是lca,因为此时lca与根节点已经全是偏爱边,所以pfa[lca]=0,而y又会跳到lca,然后就统计答案就好了。
插入或删掉x到y的一条链
原理很简单,就像序列上的插入修改一样,找到x的儿子和y的儿子,然后,把x到y这段区间旋转到一棵子树上,然后断去连边就好了。不会详见splay学习小记
由于用的不多就不贴标了。
翻转与旋转
这些操作都类似序列上的操作。不会详见splay学习小记
翻转上面讲过了。
由于本人是个蒟蒻
目前也只知道这么多了,但是这些操作在大部分题目中都够用了。
推荐题目
树的统计
染色
魔法森林