您的位置:首页 > 其它

Link Cut Tree学习小记

2016-07-03 21:01 267 查看

简介

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)。

void access(int x){
int y=0;
while(x){
splay(x,0);
f[t[x][1]]=0,pfa[t[x][1]]=x;
t[x][1]=y,f[y]=x;
pfa[y]=0;
update(x);
y=x,x=pfa[x];
}lca=y;
}


这里的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的值就好了。

换根操作

void makeroot(int x){
access(x);
splay(x,0);
bz[x]^=1;
}


把x变为当前这棵树的根节点(对于LCT来说是可以维护森林的,所以可能有很多棵树)。

bz[x]^=1是什么意思呢?

因为换了根之后,原来的根节点到x的深度都会进行翻转。所以要打一个标记。

但是翻转之后不会影响别的节点吗?

不会,因为access(x)之后,对根节点到x的路径上的点操作,只有根节点到x的路径上的点会被影响到。

旋转

void rotate(int x){
int y=f[x],z=son(x);
t[y][z]=t[x][1-z];
if(t[x][1-z])f[t[x][1-z]]=y;f[x]=f[y];
if(f[x])t[f[x]][son(y)]=x;else pfa[x]=pfa[y],pfa[y]=0;
t[x][1-z]=y;f[y]=x;
update(y);update(x);
}
void splay(int x,int y){
remove(x,y);
while(f[x]!=y){
if(f[f[x]]!=y)if(son(f[x])==son(x))rotate(f[x]);else rotate(x);
rotate(x);
}
}


把x旋转为y的子节点,splay的基本操作。

不过这里rotate要注意一个问题,因为旋转时会影响pfa,所以要更新pfa。

不会详见splay学习小记

标记下传

void down(int x){
if(bz[x]){
swap(t[x][0],t[x][1]);
if(t[x][0])bz[t[x][0]]^=1;if(t[x][1])bz[t[x][1]]^=1;
bz[x]=0;
}
}
void remove(int x,int y){
do{
d[++d[0]]=x;
x=f[x];
}while(x!=y);
while(d[0])down(d[d[0]--]);
}


这里只有翻转的标记下传,具体的据题目的不同而不同。不会详见splay学习小记

更新节点的值

void update(int x){
int l=wei[t[x][0]],r=wei[t[x][1]];
if(key[l]>key[r])wei[x]=l;else wei[x]=r;
if(key[x]>key[l]&&key[x]>key[r])wei[x]=x;
}


据题目的不同而不同。

连接两颗子树

一个节点也把他当做一个子树。

void link(int x,int y){
makeroot(x);
pfa[x]=y;
}


这里的x和y是分先后的,y深度大于要为x的深度父亲。不过假如题目并没有深度限制,比如说是一个图的建边,那么x,y就不分先后,比如说在魔法森林这题。

x一定要为当前那棵树的根节点才能连接,否则可能原本就有父亲,一连就错了。

断开两个节点的连边

void cut(int x,int y){
makeroot(x);
access(y);
splay(y,0);
t[y][0]=0,f[x]=pfa[x]=0;
update(y);
}


先让x变为根节点(那么x的深度就比y小了)才方便将x与y放入同一棵子树中。然后将y旋转为这棵子树的根节点,因为x深度比y小,那么x肯定在y的左边,断开连边就好了。注意要把pfa清空。

找到原来的父亲

int getfa(int x) {
access(x); splay(x,0);
x=t[x][0];
while(t[x][1])x=t[x][1];
return x;
}


与splay中找左右节点的原理一样。

对树中x节点到y节点进行操作

例如求极值。

方法1<推荐>

int find(int x,int y){
makeroot(x);
access(y);
splay(y,0);
return mx[y];
}


像删除一样,首先把x到y都放入同一棵子树中,因为x可能不是根节点,所以需要把其中一个旋转为根节点,我选择y(也可以选x),然后在得出答案。

方法2

int find(int x,int y){
access(x);access(y);splay(x,0);
int ans=mx[x];
if(lca==y)ans=max(ans,key[y]);
else ans=max(ans,max(mx[t[lca][1]],key[lca]));
}
}


现在就用到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学习小记

翻转上面讲过了。

由于本人是个蒟蒻

目前也只知道这么多了,但是这些操作在大部分题目中都够用了。

推荐题目

树的统计

染色

魔法森林
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: