zoukankan      html  css  js  c++  java
  • 与图论的邂逅04:LCT

      本着对数据结构这一块东西的一股兴趣,最近在集训的百忙之中抽空出来学LCT,终于学懂了这个高级玩意儿。


    前置知识:Splay和树链剖分

      Splay挺复杂的......这里就先不写,不然篇幅太大。树链剖分倒是可以大致地讲一下。

    树链剖分

      什么是树链剖分呢?就是把树给解剖成一条条的链子啦~那就先从最常用的重链剖分讲起。对于当前节点u和它的儿子构成的点集V,若size[v]=max{size[w] | w∈V},也就是以u的儿子为根的所有子树中size最大的那个儿子是v,那么称v是u的重儿子(定义size[x]:以x为根的子树所含有的点数)。如果这时把u向v连一条边,并且其它所有点也像这么做(连向它们的重儿子,叶子节点除外),由于重儿子唯一,父亲唯一,所以就会形成一条一条的链。这个链就叫重链。重链剖分是如此,类比一下就能够理解长链剖分:每个点连向子树深度最大的那个儿子。然而这两种剖法都不是LCT所使用的。LCT使用的是:实链剖分。

      实链剖分就是说,把某个节点向它的某个儿子连实边,向其它儿子连虚边,这样也能连出一条条链子。实链剖分有什么好处呢?重链剖分和长链剖分中的size和dep都是固定的,也就是说剖完一棵树之后就无法再改变。而注意刚才实链剖分定义中的一句话:"把某个节点向它的某个儿子连实边",也就是说这个"某个儿子"是可以改变的。那么实链剖分就应运而生并用于解决更灵活的问题。

      如果你觉得还不清楚,你可以看一下苯蒟蒻对重链剖分一点点浅显的理解:https://www.cnblogs.com/akura/p/10692600.html,然后类比地这些链剖分应该就可以理解了。


    LCT

      LCT使用Splay作为辅助树来维护实链,其Splay平衡的关键字为深度。也就是说,右边的点深度大于根,左边的点深度小于根。下面是LCT的相关操作:

    access(x)

      指打通x到根节点之间的路径,即建立一条从根节点连向x的实链。只需要从x一个链子一个链子地往上跳,并且把跳到的链子改一下即可。原本x到根节点路径上的点可能属于其它的实链,也就是说它的儿子不在x到根节点的路径上——这时把这对父子关系的边断掉即可:

      图中粗的是Splay维护的实链,细的是轻边。access之后成了这个样子:

      也就是说,我往上连链子的时候碰到一个点,我不管它之前连的哪条链子,都给他断掉,连上我要的链子。

    inline void access(int x){ for(int y=0;x;ch[x][1]=y,update(x),x=fa[y=x]) splay(x); }

      每次把x旋到当前Splay的根节点处,往上连边。由于改变了Splay,所以要update一下。

    makeroot(x)

      指让x成为当前的根。很简单,只需打通x到根节点的路径,然后用splay把x旋到链子顶端即可。最后不要忘了reverse(因为dep发生了改变)。

      这是access之后x和根节点之间的实链;

       这是splay了并且reverse过的这条链子。

    inline void makeroot(int x){ access(x),splay(x),rev(x); }
    //rev为reverse

    split(x,y)

      指打通x到y的路径。先用makeroot(x)让x成为树根,然后用access(y)打通y到根节点(也就是x)的路径即可。最后为了保证复杂度,把y节点splay一下。

     

      然后makeroot(x)。

     

      再access。

      最后splay一下,就不画了。(splay的部分都不画了吧......太麻烦)

    inline void split(int x,int y){ makeroot(x),access(y),splay(y); }

    findroot(x)

      寻找x所在原树的树根。先access(x)打通根到x的路径,然后splay把x旋到Splay的根。此时树的形态发生改变,所以要下传标记。由于根的深度是最小的,所以此时从x开始往Splay的左儿子一直走,最后走到的点就是根。为了保证复杂度,最后再splay一下根。

    inline int findroot(int x){ access(x),splay(x),down(x); while(ch[x][0]) x=ch[x][0],down(x); return splay(x),x; }

    link(x,y)

      从x往y连一条边。首先makeroot(x)让x成为根,此时link(x,y)是合法的当且仅当findroot(y)!=x即y所在原树的根不是x。这时只需让fa[x]=y即可。不要忘了update(y)更新一下儿子的信息。

    inline void link(int x,int y){ makeroot(x); if(findroot(y)!=x) fa[x]=y,update(y); }

    cut(x,y)

      切断x与y之间的边。首先还是makeroot(x)让x成为根,此时cut(x,y)是合法的当且仅当findroot(y)=x并且fa[y]=x并且ch[y][0]=0。最后一个条件是什么意思呢?由于Splay中左儿子的深度小于根,所以若ch[y][0]不为0,则说明还有比y深度更小的点介于x与y之间,此时不能删边。判断合法之后,修改两个节点的信息,令ch[x][1]=0,fa[y]=0即可。还是不要忘了更新一下x的儿子的信息。

    inline void cut(int x,int y){ makeroot(x); if(findroot(y)==x&&fa[y]==x&&!ch[y][0]) ch[x][1]=fa[y]=0,update(x); }

      以上就是LCT的最基础的操作(苯蒟蒻还没有学其它的高级操作)。难理解的部分都画了图,应该是能看懂的。

      最后对着模板题放个代码:

    给定n个点以及每个点的权值,要你处理接下来的m个操作。操作有4种。操作从0到3编号。点从1到n编号。

    0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。

    1:后接两个整数(x,y),代表连接x到y,若x到y已经联通则无需连接。

    2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。

    3:后接两个整数(x,y),代表将点x上的权值变成y。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define maxn 300001
    using namespace std;
    typedef long long ll;
    
    inline int read(){
        register int x(0),f(1); register char c(getchar());
        while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
        while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    inline void swap(ll &a,ll &b){ register int tmp; tmp=a,a=b,b=tmp; }
    
    int  ch[maxn][2],fa[maxn],lz[maxn];
    ll sum[maxn],val[maxn];
    
    inline int isroot(int x){ return ch[fa[x]][0]==x||ch[fa[x]][1]==x; }
    inline int getson(int x){ return ch[fa[x]][1]==x; }
    inline void update(int x){ sum[x]=val[x]^sum[ch[x][0]]^sum[ch[x][1]]; }
    inline void rev(int x){ lz[x]^=1; swap(ch[x][0],ch[x][1]); }
    inline void down(int x){ if(lz[x]){ if(ch[x][0]) rev(ch[x][0]); if(ch[x][1]) rev(ch[x][1]); } lz[x]=0; }
    inline void rotate(int x){
        int y=fa[x],z=fa[y],k=getson(x),w=ch[x][k^1];
        if(isroot(y)) ch[z][getson(y)]=x;fa[x]=z;
        ch[x][k^1]=y,fa[y]=x;
        if(w) fa[w]=y; ch[y][k]=w;
        update(y);
    }
    inline void splay(int x){
        int y,z,stack[maxn]; y=x,z=0,stack[++z]=y;
        while(isroot(y)) y=fa[y],stack[++z]=y; while(z) down(stack[z--]);
        for(y=fa[x]; isroot(x); rotate(x),y=fa[x]) if(isroot(y)) rotate(getson(y)^getson(x)?x:y); update(x);
    }
    
    inline void access(int x){ for(int y=0;x;ch[x][1]=y,update(x),x=fa[y=x]) splay(x); }
    inline void makeroot(int x){ access(x),splay(x),rev(x); }
    inline void split(int x,int y){ makeroot(x),access(y),splay(y); }
    inline int findroot(int x){ access(x),splay(x),down(x); while(ch[x][0]) x=ch[x][0],down(x); return splay(x),x; }
    inline void link(int x,int y){ makeroot(x); if(findroot(y)!=x) fa[x]=y,update(y); }
    inline void cut(int x,int y){ makeroot(x); if(findroot(y)==x&&fa[y]==x&&!ch[y][0]) ch[x][1]=fa[y]=0,update(x); }
    
    int main(){
        int n=read(),m=read();
        for(register int i=1;i<=n;i++) sum[i]=val[i]=read();
        for(register int i=1;i<=m;i++){
            int op=read(),x=read(),y=read();
            if(op==0) split(x,y),printf("%lld
    ",sum[y]);
            if(op==1) link(x,y);
            if(op==2) cut(x,y);
            if(op==3) splay(x),val[x]=y;
        }
        return 0;
    }
  • 相关阅读:
    自我介绍
    币值转换
    打印沙漏
    对我影响最大的三位老师

    pta
    pta-3
    学习计划
    对我有影响的三个老师
    介绍自己
  • 原文地址:https://www.cnblogs.com/akura/p/10786719.html
Copyright © 2011-2022 走看看