博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[2018.12.19]动态树LCT
阅读量:5913 次
发布时间:2019-06-19

本文共 5642 字,大约阅读时间需要 18 分钟。

总算学会了...NOIp2018之前就开始学了...模板一直过不去...

需要先学会。

现在切入正题。

LCT的概况

一种数据结构。(废话)

可以均摊\(O(logn)\)维护一个森林,支持的树上任意路径的查询,两棵树的连接、断开,单点的修改。

LCT的实现基于Splay。

大概长成下面这样↓

图炸了QWQ

(图片来自,下同)

LCT中的边分为实边和虚边,每一个节点连向它的孩子的边中只有一条实边。

实边需要记录在这条边的两个端点上(即孩子记父亲,父亲记孩子),虚边只需要记录在孩子上(即孩子记父亲即可)。

其中实边(实线的边)连接的一组点构成一个Splay,大小依据为深度。

Splay的根的父亲指针指向这棵Splay中深度最小的节点在原树中的父亲(这是一条虚边)。

比如上面这个LCT的Splay长这样(当然和Splay的形态没关系)↓

图炸了QWQ

具体操作

LCT中的Splay必须维护的是翻转标记,其他的视题目而定。

首先是一个前置操作,判断点\(y\)是否是Splay的根(因为即使是Splay的根也可能有在别的Splay中有父亲,所以并不是没有父亲的才是根):

bool Isroot(int x){    int y=t[x].f;    return !(t[y].c[0]==x||t[y].c[1]==x);}

本文的Splay节点定义如下:

struct node{    int f,c[2],tag,...;    //f是父亲,c[0/1]是左/右孩子,tag是翻转标记}t[Size];

Splay的操作有些会有所修改,如下:

上/下传(并没有不一样):

void Upd(int x){//视具体情况而定    //something...}void Psd(int x){//主要是下传翻转标记    if(t[x].tag){        swap(t[x].c[0],t[x].c[1]);        if(t[x].c[0])t[t[x].c[0]].tag^=1;        if(t[x].c[1])t[t[x].c[1]].tag^=1;        t[x].tag=0;    }}

旋转:

void Rotate(int x){    int y=t[x].f,z=t[y].f;    Psd(y);    Psd(x);    int c=t[y].c[1]==x;    if(!Isroot(y)){//注意这里要判断y是否为Splay的根(否则z可能在另一颗Splay中或者不存在)        int gc=t[z].c[1]==y;        t[z].c[gc]=x;    }    t[x].f=z;    t[y].c[c]=t[x].c[c^1];    t[t[x].c[c^1]].f=y;    t[x].c[c^1]=y;    t[y].f=x;    Upd(y);    Upd(x);}

伸展:

void Splay(int x){    Psd(x);    while(!Isroot(x)){//这里同上        int y=t[x].f,z=t[y].f;        if(Isroot(y))Rotate(x);        else{            Psd(z);            Psd(y);            int c=t[y].c[1]==x,gc=t[z].c[1]==y;            if(c==gc)Rotate(y);            else Rotate(x);            Rotate(x);        }    }}

接下来终于开始讲LCT了

1.访问操作\(Access(x)\)

使\(x\)与当前LCT的根存在由实边组成的路径,并且\(x\)是路径中深度最大的点。

之后所有操作的基础。

我们从\(x\)开始,一路向父亲寻找,每次将节点伸展为所在Splay的根,将该节点的父亲的所有连向儿子的边变成虚边,将该节点的父亲和节点本身的边变成实边(该节点是其父亲的右儿子,因为深度大于它的父亲)。

当然为了保证\(x\)是深度最大的,一开始要把\(x\)的右儿子断开。

我个人喜欢递归实现,调用方式为\(Access(x,0)\)

code:

void Access(int x,int lst){    if(!x)return;    Splay(x);    t[x].c[1]=lst;    Upd(x);    Access(t[x].f,x);}

2.换根操作\(Makeroot(x)\)

\(x\)换为LCT的根。

我们需要\(Access(x)\),此时\(x\)与LCT的根直接相连,而且是它到根的路径上深度最大的。

我们要让它变成根,就是路径中深度最小的点,怎么办?

直接翻转Splay不就好了。。。

为了确定翻转标记标记的位置,我们再将\(x\)换为所在Splay的根。

code:

void Makeroot(int x){    Access(x,0);    Splay(x);    t[x].tag^=1;}

3.??操作\(Split(x,y)\)

难以概括的操作。。。就是将\(x\)\(y\)的路径提取出来方便询问。

\(x\)换为LCT的根,再\(Access(y)\),就得到了\(x\)\(y\)的路径。

\(Splay(y)\),就可以在\(y\)节点上获取路径信息了。

code:

void Split(int x,int y){    Makeroot(x);    Access(y,0);    Splay(y);}

4.找根操作\(Findroot(x)\)

找到\(x\)所在LCT的根,主要用于判断两点联通性。

\(Access(x)\),将\(x\)和它在LCT中的根放在同一个Splay中,再\(Splay(x)\),将\(x\)变为所在Splay的根。

然后\(x\)所在LCT的根必然是深度最小的,所以一直跳左孩子即可。

(记得下传标记哦)

code:

int Findroot(int x){    Access(x,0);    Splay(x);    Psd(x);    while(t[x].c[0]){        x=t[x].c[0];        Psd(x);    }    Splay(x);//维持Splay的平衡    return x;}

5.连接操作\(Link(x,y)\)

\(x,y\)之间连一条边。

首先如果\(x,y\)已经联通(\(Findroot(x)=Findroot(y)\)),操作不合法,直接退出即可。

否则,让\(x\)成为所在LCT的根,将它和\(y\)之间连一条虚边(即让\(x\)的父亲为\(y\))。

code:

void Link(int x,int y){    Makeroot(x);    if(Findroot(y)!=x)t[x].f=y;}

6.断边操作\(Cut(x,y)\)

断开\(x\)\(y\)之间的边(如果有的话)。

\(Makeroot(x)\)后,\(x\)是LCT的根,而\(y\)深度必然大于\(x\),所以会在右子树中,深度相差1(如果存在这条边),即在Splay中\(x\)的右孩子。

如果\(y\)\(x\)直接连边,那么首先判断联通性,然后看看\(y\)是否是\(x\)的右孩子,并且\(y\)没有左孩子(如果有左孩子说明\(x\)\(y\)之间还有点)。

存在的话直接断边就好了。

code:

void Cut(int x,int y){    Makeroot(x);    Psd(x);    if(Findroot(y)==x&&t[y].f==x&&!t[y].c[0]){        t[x].c[1]=t[y].f=0;        Upd(x);    }}

结束。

最后给出模板题代码:

#include
using namespace std;struct node{ int f,c[2],v,xs,tag;}t[300010];int n,m,op,u,v;void PrintLCT(){ for(int i=1;i<=n;i++){ cout<
<<"<-node "<
<<" l->"<
<<" r->"<
<<"\n"; }}bool Isroot(int x){ int y=t[x].f; return !(t[y].c[0]==x||t[y].c[1]==x);}void Upd(int x){ t[x].xs=t[x].v^t[t[x].c[0]].xs^t[t[x].c[1]].xs;}void Psd(int x){ if(t[x].tag){ swap(t[x].c[0],t[x].c[1]); if(t[x].c[0])t[t[x].c[0]].tag^=1; if(t[x].c[1])t[t[x].c[1]].tag^=1; t[x].tag=0; }}void Rotate(int x){ int y=t[x].f,z=t[y].f; Psd(y); Psd(x); int c=t[y].c[1]==x; if(!Isroot(y)){ int gc=t[z].c[1]==y; t[z].c[gc]=x; } t[x].f=z; t[y].c[c]=t[x].c[c^1]; t[t[x].c[c^1]].f=y; t[x].c[c^1]=y; t[y].f=x; Upd(y); Upd(x);}void Splay(int x){ Psd(x); while(!Isroot(x)){ int y=t[x].f,z=t[y].f; if(Isroot(y))Rotate(x); else{ Psd(z); Psd(y); int c=t[y].c[1]==x,gc=t[z].c[1]==y; if(c==gc)Rotate(y); else Rotate(x); Rotate(x); } }}void Access(int x,int lst){ if(!x)return; Splay(x); t[x].c[1]=lst; Upd(x); Access(t[x].f,x);}void Makeroot(int x){ Access(x,0); Splay(x); t[x].tag^=1;}void Split(int x,int y){ Makeroot(x); Access(y,0); Splay(y);}int Findroot(int x){ Access(x,0); Splay(x); Psd(x); while(t[x].c[0]){ x=t[x].c[0]; Psd(x); } Splay(x); return x;}void Link(int x,int y){ Makeroot(x); if(Findroot(y)!=x)t[x].f=y;}void Cut(int x,int y){ Makeroot(x); Psd(x); if(Findroot(y)==x&&t[y].f==x&&!t[y].c[0]){ t[x].c[1]=t[y].f=0; Upd(x); }}int Query(int x,int y){ Split(x,y); return t[y].xs;}void Change(int x,int y){ Splay(x); t[x].v=y; Upd(x);}int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&t[i].v); t[i].xs=t[i].v; } for(int i=1;i<=m;i++){ scanf("%d%d%d",&op,&u,&v); if(op==0)printf("%d\n",Query(u,v)); else if(op==1)Link(u,v); else if(op==2)Cut(u,v); else Change(u,v); } return 0;}

转载于:https://www.cnblogs.com/xryjr233/p/10537333.html

你可能感兴趣的文章
python利用session保持登录状态
查看>>
线性表>>顺序表--->逆置所有元素
查看>>
python shelve模块
查看>>
redis配置说明
查看>>
<iOS>关于Xcode上的Other linker flags
查看>>
数组的5个迭代方法
查看>>
C++以16进制输入10进制输出
查看>>
2018 German Collegiate Programming Contest (GCPC 18)
查看>>
前端之jquery
查看>>
Linux启动初始化配置文件浅析
查看>>
启动mysql5.7异常The server quit without updating PID file [FAILED]sql/data/***.pi根本解决方案...
查看>>
配置browser-sync 浏览器同步测试工具
查看>>
Source code for Bayesian based CS and blind debluring
查看>>
静态类和非静态类
查看>>
Redis学习系列三List列表
查看>>
关于日志表的自动创建及分表储存
查看>>
topcoder srm 315 div1
查看>>
【super vlan的配置】
查看>>
洛谷P1443 马的遍历
查看>>
日期字符串格式化
查看>>