LCT动态树学习小记

总起

之前一直不怎么会LCT,然后THUWC的时候····看着知道怎么写就是不会打····所以就学一发,顺便搞了搞其他的数据结构。

介绍

Link-cut Tree即动态树,能够解决很多动态树问题,而且树的结构变化多端。
LCT跟树链剖分有很像的地方,它也要维护重链,也有类似的轻边。用splay,要分清splay和原图的边。
具体的,splay的关键字是点的深度,左边深度小。如何记录这条重链顶端的父亲是谁呢?记在splay顶端就好了,到时候rotate的时候让他一直在splay根上。
它的思想基于几个操作。
1,Access(x),把原树上x到根路径上的所有点放到同一颗splay里面.
2,Makeroot(x),让x成为原树的根,就是把原树形态改一改
3,Cut(x,y),切断原树上相邻的节点x,y的边.
4,Link(x,y),让x,y连一条树边,成为属于原图的同一棵树。

一些定义

1,splay(x,y)表示x旋转到为y的直接儿子,y=0则x为根。
2,tup[x]为x在原树中的父亲,即一条轻边。
3,up[x]为splay中的x的父亲。

Access(x)

效果:让x到根的所有点成为一条重链,且这条重链只有x到根的所有点,即其他点都断掉。
操作:
1,splay(x,0),splay上,断开右子树,右子树的顶端记录树中轻边连向x。
2,令last=x,x=tup[x],执行操作1,并让last所在子树成为x的右子树。(splay上)
3,重复操作2直至做完根。

Makeroot(x)

效果:让x成为原树的根。
操作:
1,Acccess(x)
2,splay(x,0) [一个无用步骤,万一有玄学加成呢?]
3,在splay上,给x打翻转标记
为什么可以这样呢?因为access(x)之后,原树其他点到这个重链上的所有点都是轻边了,无论是这个重链的谁作为根,轻边的信息都不会变质。而x为重链的最深点,把它作为根,那么深度顺序就反了过来,所以翻转。

Cut(x,y)

前提:x,y有树边
操作:
1,Makeroot(x)
2,Access(y)
3,splay(y,0)
4,在splay上,把y的左子树断掉。
解释:
我们不知道x,y谁是谁的父亲,所以makeroot(x),那么此时x肯定是y的父亲了。为了其他点的信息不被破坏,我们断掉y所在的重链,让x,y于同一重链,把y的左子树断掉,即x和y断掉了,此时tup[y]肯定是0,因为x是根。
实际上y这颗splay只有两个点,一个x一个y,可能可以让操作更简单。但上述操作已经很简洁了。

Link(x,y)

前提:x,y无树边相连。
操作:
1,makeroot(x)
2,splay(x,0)
3,tup[x]=y
解释:
如果x或者y不是原树上的根的话,强行连之后x或y就有两个父亲了····所以不能直接连,要makeroot(x),然后我们要连轻边,但是不知道谁是x所在splay的根,我们就splay(x,0)。最后才连轻边。

时间复杂度

据WerKeyTom_FTD的ppt等势能分析,总时间复杂度是O(nlogn)的,不过常数巨大····这里懒得具体写了。

一些思考

灵活地makeroot和access是十分有用的,例如查找x到y的简单路径,我们makeroot(x)再access(y)就让整个路径位于同一重链了。

本人实现的一些小细节

本来应该写在代码后面,但是代码好长,先写出来。
1,access可以一个循环搞定。
2,最好在access(x)的最后加上splay(x,0),因为有时别的操作access之后的splay容易忘掉。
3,splay(x,y)之前要进行remove(x,y)操作,怕翻转标记没传完就splay就断开了或者旋转得很恶心之类的鬼畜情况。
4,remove了之后不知道还要不要down,反正不卡常的话打了保险,不过有几道题我不打没事····

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=200005;
int tr[N][2],tag[N],up[N],tup[N],sta[N],st;
int dad[N],rx,ry;
int xx,yy,ans,pp,x,y,z,i,j,n,m,pos;
int pd(int x)
{
    return tr[up[x]][1]==x;
}
void down(int x)
{
    if (!tag[x]) return;
    swap(tr[x][0],tr[x][1]);
    tag[tr[x][0]]^=1;
    tag[tr[x][1]]^=1;
    tag[0]=tag[x]=0;
}
void update(int x)
{
    int ls=tr[x][0],rs=tr[x][1];
    down(ls);
    down(rs);
    //其他维护
}
void rotate(int x)
{
    int y=up[x],z;
    z=pd(x);
    up[x]=up[y];
    if (up[x]) tr[up[x]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[y][z]) up[tr[y][z]]=y;
    tr[x][1-z]=y;
    up[y]=x;
    update(y);
    update(x);
    if (tup[y]) tup[x]=tup[y];
    tup[y]=0;
}
void remove(int x,int y)
{
    st=1;
    sta[1]=x;
    while (x!=y)
        sta[++st]=(x=up[x]);
    while (st)
        down(sta[st--]);
}
void splay(int x,int y)
{
    remove(x,y);
    while (up[x]!=y)
    {
        if (up[up[x]]!=y)
        {
            if (pd(x)==pd(up[x]))
                rotate(up[x]);
            else rotate(x);
        }
        rotate(x);
    }
}
void access(int x)
{
    int last=0,z=x;
    while (x)
    {
        splay(x,0);
        up[tr[x][1]]=0;
        if (tr[x][1]) tup[tr[x][1]]=x;
        tr[x][1]=last;
        tup[last]=0;
        if (last) 
            up[last]=x;
        update(x);
        last=x;
        x=tup[x];
    }
    splay(z,0);
}
void makeroot(int x)
{
    access(x);
    tag[x]^=1;
}
void cut(int x,int y)
{
    makeroot(x);
    access(y);
    if (tr[y][0]) up[tr[y][0]]=0;
    tr[y][0]=0;
}
void link(int x,int y)
{
    makeroot(x);
    splay(x,0);
    tup[x]=y;
}
int main(){}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值