zoukankan      html  css  js  c++  java
  • [2018.12.19]动态树LCT

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

    需要先学会Splay

    现在切入正题。

    先放模板题链接

    LCT的概况

    一种数据结构。(废话)

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

    LCT的实现基于Splay。

    大概长成下面这样↓

    图炸了QWQ

    (图片来自这位dalao的博客,下同)

    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<bits/stdc++.h>
    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<<t[i].f<<"<-node "<<i<<" l->"<<t[i].c[0]<<" r->"<<t[i].c[1]<<"
    ";
        }
    }
    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
    ",Query(u,v));
            else if(op==1)Link(u,v);
            else if(op==2)Cut(u,v);
            else Change(u,v);
        }
        return 0;
    }
    
  • 相关阅读:
    年轻就该多尝试,教你20小时Get一项新技能
    小程序定位地图模块全系列开发教学(超详细)
    javaScript学习笔记之break 和 continue 语句对比
    数据处理的两个基本问题03 零基础入门学习汇编语言40
    数据处理的两个基本问题01 零基础入门学习汇编语言38
    VMware虚拟机安装XP系统演示
    数据处理的两个基本问题01 零基础入门学习汇编语言38
    数据处理的两个基本问题02 零基础入门学习汇编语言39
    数据处理的两个基本问题02 零基础入门学习汇编语言39
    VMware虚拟机安装XP系统演示
  • 原文地址:https://www.cnblogs.com/xryjr233/p/10537333.html
Copyright © 2011-2022 走看看