定义
我们来看LCT的定义是什么。
LCT用Splay维护树的剖分,一棵树被剖分成为虚边和实边。
其中每个点必须连着一条实边,也就是说,如果我们把实边和与实边相连的节点提出来,树里面不会剩下任何节点。
实边和实边相连的节点组成的联通块,被称为实链,我觉得实链这个说法实在不是很准确,因为实链的点深度是严格依次加1的,所以下图不是LCT。
可以想到将节点深度从小到大地维护一个Splay,这个Splay的中序遍历即是按照深度排列的节点。
比如ABCD四个点组成的Splay长成这个样子:
首先,一个最重要的结论是,认父不认子:
比如我们这里画箭头表示认父亲/孩子,对于一个节点,它只认和它有实链相连的节点作为孩子。但是所有节点除了根节点都有一个父亲,Splay里面的节点有父亲,每条实链的根节点认树上的父亲作为根节点。
操作
下面谈一下操作:
Splay操作
来自yyb。
1 |
|
相信Splay操作没什么好说的,这里写的是单旋。
如果不知道Splay是什么,你可以把它理解为把节点旋转到这棵Splay的根的一种操作。
Access操作
可以理解为打通到根节点的实链的一种操作。
1 | void Access(int x){ |
位于下一条实链的顶部,位于上一条实链的中间某个节点。
Splay(x)之后,因为位于底部,是最后一个,所以右子树是那条实链下面的部分。
所以我们可以把接在的右子树上(可以理解为抢来一个实链),还要pushup维护信息。
注意:Access操作并不会形成一个以x为根节点的Splay,如果需要,请你再Splay一次
MakeRoot操作
1 | void MakeRoot(int x){ |
Access(x),Splay(x)之后,我们得到的是一棵根节点为,只有左子树的一棵Splay,这棵Splay中序遍历最靠前的就是,最靠后的是。(考虑一下他们的深度关系)
如果我们reverse(x),即把左右翻转,就会跑到的位置,就会跑到的位置。(也可以看成将中序遍历翻转)
这样我们就可以将作为根。
Split操作
1 | void Split(int x,int y){ |
Split操作可以看成打通一条从到的实链,其中实链的Splay根节点是。
这个操作比较简单,不多说了。
Link操作
1 | void Link(int x,int y){//保证x,y不在一个连通块里面 |
最关键的Link操作,也是最简单的,将作为所在子树的根后,只要往连一条父亲边即可。
Cut操作
1 | void Cut(int x,int y){//保证有这条边 |
Cut操作稍微复杂,我们先Split(x,y),分成了下图这样的一条实链。
接下来我们需要断掉这条边。然后维护的子树大小即可。
FindRoot操作
1 | int FindRoot(int x){ |
我们Access(x),Splay(x),形成了一棵以为根节点的树。
接下来我们要找在中序遍历序列里面最前的一个节点,即根节点,因为树高是的,我们可以暴力跳。
接下来还要再Splay一下,以防被卡。