Category 最新活动

LCT总结(个人笔记)

柒葉灬

·

2019-03-10 14:46:24

·

个人记录

LCT总结

前置技能:splay

介绍:

LCT即 Link-Cut-Tree,

其中Link指连接,Cut指断开,Tree则是一棵树。

说白了就是一棵动态树,

可以动态删除边,添加边。

擅长处理链上的问题,

但是子树问题也可以解决,后面会有例题。

当然,作为一个优秀的数据结构LCT,

肯定不能像普通的树一样暴力跳父亲,

肯定要有优秀的方法快速找根。

但普通的树是用 倍增 和 重链剖分。

(或许还有长链剖分

而这两种算法都不能修改。

所以我们用的是另一种方法:实链剖分

实链剖分

这里先介绍一下什么是 实链剖分 。

实际上就是选一个儿子与自己的边当做实边,

其他边都是虚边

实边需要满足什么条件?

实边不需要任何条件,

就是自己随便选的,

就是因为是随便选的,所以才可以动态连边删边。

这些实边就形成了实链。

当然也可以都是虚边

我之前在一篇博客里面看到一句话,

很好的概括了实边和虚边的区别:

认子不认父

即每个点不用管自己和父亲连的是实边还是虚边,

只要记录与哪一个儿子连实边就行了。

如果需要判断自己和父亲是不是连实边

只需要判一下父亲的实儿子是不是自己就行了

感觉有什么怪怪的???

那么有了实链之后有什么用?

怎么把实链用在LCT上?

想一下如果是重链剖分的话,应该怎么找根呢?

我们可以一直跳到实链的顶端,直到到达根为止。

但是这样还有几个问题需要解决:

怎么保证跳的实链的次数不会过大?

怎么跳到每条链的顶端的父亲?

第一个问题:怎么保证跳的实链的次数不会过大?

我们可以每次把当前的点和根变成一条实链,

以此来保证复杂度的合理性。(感性理解)

第二个问题:怎么跳到每条链的顶端的父亲?

重链剖分里面是直接记录每一个点的top指向哪里。

而实链剖分就不能这么做,因为top随时可能会变。

这时候就要用到最核心的知识了,

把每一条实链用一个splay来表示:

左儿子深度都比当前点浅

右儿子深度都比当前点深

splay当中最左边的数就是链的顶端了。

但,链顶的点不一定是splay的根怎么办?

即,可能链顶的点的fa表示的是splay当中的fa

我们注意到,splay当中,根是没有父亲的。

所以我们可以这么做,

每个splay的根的fa表示链顶的点在树中的父亲

做个图以便理解:

其中加粗的是实边,其他的是虚边。

而实际上每一个实链是用splay来表示的,

所以可能实际上son和fa的关系是这样的。

其中每一个框就是一个splay

?mmp为什么写了半天的概念

接下来就是怎么设计代码了。

多了一个判断是不是$splay$的根的函数$noroot

bool noroot(int x){

return son[fa[x]][0]==x||son[fa[x]][1]==x;

}//如果不是splay的根返回true;

首先,LCT当中的splay的rotate和splay

是不能复制普通splay的,

有些不同。

rotate操作

void rotate(int x){

int y=fa[x];

int z=fa[y];

int k=(son[y][1]==x);

if(noroot(y))son[z][(son[z][1]==y)]=x;

fa[x]=z;

son[y][k]=son[x][k^1];

fa[son[x][k^1]]=y;

son[x][k^1]=y;

fa[y]=x;

up(y);up(x);

}

为什么需要判断noroot(y)呢?

因为如果y是根则说明z不在x这个splay内

如果不加则会改变z的实儿子,故错。

splay操作

void splay(int x){

int u=x,top=0;

stk[++top]=u;

while(noroot(u))stk[++top]=u=fa[u];

while(top)down(stk[top--]);

//即先down完之后再splay

while(noroot(x)){

int y=fa[x];

int z=fa[y];

if(noroot(y))

(son[z][1]==y)^(son[y][1]==x)?rotate(x):rotate(y);

rotate(x);

}

}

以上都是处理实链的操作,那么怎么造出实链呢?

下面这个操作就是LCT的核心操作了。

access操作

void access(int x){

for(int y=0;x;y=x,x=fa[x])

splay(x),son[x][1]=y,up(x);

}

这段代码什么意思呢?

即造出x到root这条实链,之前的其他实边变为虚边。

循环里面是把x与y连上实边,之前的实边被覆盖。

注意链是从root到x的,

所以x实际上位于实链的最底端,splay的最右端。

接下来的代码想想就都能明白了

makeroot操作

void makeroot(int x){

access(x);splay(x);pushr(x);

}

把x变成树的根。

findroot操作

int findroot(int x){

access(x);splay(x);

while(son[x][0])down(x),x=son[x][0];

splay(x);//保证复杂度

return x;

}

还有一个操作可以把要修改的链给拎出来

split操作

void split(int x,int y){

makeroot(x);access(y);splay(y);

//直接对y节点进行询问即可

}

最后就是连边和删边操作了

link操作

所连的边可能已经存在了,那么这么写

bool link(int x,int y){

makeroot(x);

if(findroot(y)==x)return false;

fa[x]=y;//直接连接虚边

return true;

}

如果数据中保证边合法,那么可以这么写

void link(int x,int y){

makeroot(x);

fa[x]=y;

}

cut操作

所删的边可能不存在,需要这么写

bool cut(int x,int y){

makeroot(x);

if(findroot(y)!=x||fa[y]!=x||son[y][0])return false;

son[x][1]=fa[y]=0;

up(x);

return true;

}

如果数据中保证边合法,那么可以这么写

void cut(int x,int y){

split(x,y);

son[y][0]=fa[x]=0;

up(y);

}

那么LCT基本就已经讲完了,接下来就是做题经验了。

LCT在基环树当中的应用

基环树当中有一个环,但显然树里面不能有环,

所以可以用LCT把基环树变成树。

即把环中的一个点变成根,

再把连成环的那条边单独记录下来,

如果环被破坏掉了则连回去。

LCT专题L

LCT维护子树信息

只不过略微麻烦一点,

就是多维护一个虚儿子的信息,

在$access$的时候注意修改就行了。

[LCT专题K](https://cn.vjudge.net/contest/282972#problem/K)

------

END

top
Copyright © 2088 《王者新服》限时活动专题站 All Rights Reserved.
友情链接