• 树上数据结构——LCT


    树上数据结构——LCT

    概述

    LCT是一种强力的树上数据结构,支持以下操作:

    1. 链上求和
    2. 链上求最值
    3. 链上修改
    4. 子树修改
    5. 子树求和
    6. 换根
    7. 断开树上一条边
    8. 连接两个点,保证连接后仍然是一棵树。

    基本概念

    LCT是对树的实链剖分,即把所有边划分为实边和虚边

    类似于重链剖分,每个点连向子节点中的实链至多只会有一条,把这条实边连向的儿子叫做实儿子

    把一些实边连接的点构成的链叫做实链,容易发现实链之间没有共同点

    需要注意的是一个不在实边上的点(一些叶节点)也视为一条没有实边的实链

    于是实链之间一定是用虚边链接的

    要涉及动态删连边操作,于是使用splay来维护一条实链,splay是LCT的辅助树

    此处splay的深度按中序遍历严格递增

    由于用splay维护,LCT的实边是动态的,可以改变

    核心操作

    access(x):让x到根节点的所有边均为实边,并且x没有实儿子

    这个推荐flash_hu的博客,简单易懂

    稍微说一下,每次操作先把当前要连的点splay到当前splay的根,由于splay中深度按中序遍历递增,此时根的右儿子一定是之前连的实链,需要去掉

    于是把之前的点连到当前根的右儿子就行了

    注意此时一些(fa,son,isroot)之类的信息改变了,需要(push)_(up)

    void access(int x){
        for(int y=0;x;y=x,x=fa[x]){ //y是之前的根,x是当前需要连的点
            splay(x); ch[x][1]=y;
            push_up(x);
        }
    }
    

    其他操作

    1. makeroot

      换根操作

      access(x)之后x是深度最大的点

      所以splay(x)之后,x在splay中一定没有右子树,这个时候翻转整个splay,所有点的深度就都倒过来了,x成为深度最小的点,即为根节点

      void pushr(int x){
          swap(ch[x][0],ch[x][1]);
          r[x]^=1;
      }
      void makeroot(int x){
          access(x); splay(x);
          pushr(x);
      }
      
    2. findroot

      找所在树的树根,可以用来判断两点之间的连通性(两点所在树相同则有唯一相同根

      int findroot(int x){
          access(x);splay(x);
          while(c[x][0]) push_down(x),x=ch[x][0];//寻找深度最小的点,此处push_down是为了x到跟的标记放完,好判连通性
          splay(x);//多多splay有益健康
          return 0;
      }
      
    3. split

      把一条路径拉成一个splay

      void spilt(int x,int y){
          makeroot(x);access(y);
          splay(y);
      }
      
    4. link

      连一条边,保证连完还是一棵树

      不保证合法:

      int link(int x,int y){
          makeroot(x);
          if(findroot(y)==x) return 0;
          fa[x]=y; //把x作为y的儿子
          return 1;
      }
      

      保证合法:

      void link(int x,int y){
          makeroot(x);
          fa[x]=y;
      }
      

      此处连的边是虚边(感受到实链剖分的方便了罢

    5. cut

      断边

      保证存在:

      void cut(int x,int y){
          split(x,y);
          fa[x]=ch[y][0]=0;
          push_up(y);
      }
      

      不存在此边的时候是什么情况呢?

      先把x给(makeroot)到根

      1. x和y不连通 ((findroot)

      2. 在同一splay中而没有直接连边 ((f[y]==x)(!c[y][0]))

        (考虑其他的点在哪里,在findroot之后x到了根节点,如果x和y之间有点,只能是在y到根的路径上或者y的左儿子上)

      int cut(int x,int y){
          makeroot(x);
          if(findroot(y)!=x||fa[y]!=x||ch[y][0]) return 0;
          fa[y]=ch[x][1]=0;
          push_up(x);
          return 1;
      }
      
    6. nroot

      naiive的操作,判断此点是否不是当前splay的根节点

      int nroot(int x){
          return (ch[fa[x]][1]==x||ch[fa[x]][0]==x);
      }
      
    7. splay 的特殊性

      此处splay的标记一定要从上往下放,也就是先开个栈把标记放完再旋转

    完整模板

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define reg register int 
    #define il inline 
    #define ls ch[x][0]
    #define rs ch[x][1]
    int read(){
    	int x=0,pos=1;char ch=getchar();
    	for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
    	for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    	return pos?x:-x;
    }
    const int N = 400025;
    int fa[N],ch[N][2],v[N],s[N],st[N],r[N];
    il int nroot(int x){
    	return ch[fa[x]][1]==x||ch[fa[x]][0]==x;
    }
    il int get(int x){
    	return ch[fa[x]][1]==x;
    }
    il void push_up(int x){
    	s[x]=v[x]^s[ls]^s[rs];
    }
    il void pushr(int x){
    	swap(ls,rs);r[x]^=1;
    }
    il void push_down(int x){
    	if(r[x]){
    		if(ls) pushr(ls);
    		if(rs) pushr(rs);
    		r[x]=0;
    	}
    }
    il void rotate(int x){
    	int f=fa[x],g=fa[f],kx=get(x),kf=get(f);
    	if(nroot(f)) ch[g][kf]=x;
    	fa[x]=g;
    	/*if(ch[x][kx^1])*/ fa[ch[x][kx^1]]=f;
    	ch[f][kx]=ch[x][kx^1];
    	fa[f]=x;ch[x][kx^1]=f;
    	push_up(f);push_up(x);
    }
    il void push(int x){
    	if(nroot(x)) push(fa[x]);
    	push_down(x);
    }
    il void splay(int x){
    	int f,g;
    	push(x);//fuctional stack
    	while(nroot(x)){
    		f=fa[x],g=fa[f];
    		if(nroot(f)){
    			rotate(get(x)==get(f)?f:x);
    		}
    		rotate(x);
    	}
    }
    il void access(int x){
    	for(reg y=0;x;y=x,x=fa[x]){
    		splay(x);rs=y;push_up(x);
    	}
    }
    il void makeroot(int x){
    	access(x);splay(x);pushr(x);
    }
    il void spilt(int x,int y){
    	makeroot(x);
    	access(y);splay(y);
    }
    il int findroot(int x){
    	access(x);splay(x);
    	while(ls) x=ls;
    	splay(x);
    	return x;
    }
    il void link(int x,int y){
    	makeroot(x);
    	if(findroot(y)!=x) fa[x]=y;
    }
    il void cut(int x,int y){
    	makeroot(x);
    	if(findroot(y)==x&&fa[y]==x&&!ch[y][0]){
    		fa[y]=ch[x][1]=0;
    		push_up(x);
    	}
    }
    int n,m;
    int main(){
    	n=read();m=read();
    	for(reg i=1;i<=n;i++){
    		v[i]=read();
    	}
    	for(reg i=1;i<=m;++i){
    		int opt=read(),x=read(),y=read();
    		if(opt==0){
    			spilt(x,y);printf("%d
    ",s[y]);
    		}else if(opt==1){
    			link(x,y);
    		}else if(opt==2) cut(x,y);
    		else splay(x),v[x]=y;
    	}
    	return 0;
    }
    

    之后可能会补自己做的LCT题(咕


    在创作本文的过程中,参考了以下文章:

  • 相关阅读:
    算法导论(1)堆排序
    Opencv--HoughCircles源码剖析
    数据结构算法应用C++语言描述——(1)C++基础知识
    Java编程的23种设计模式
    团队建设
    管理方法论和角色认知
    压力测试:怎样设计全链路压力测试平台
    09-数据库优化方案(二):写入数据量增加时,如何实现分库分表
    08-数据库优化方案(一):查询请求增加时,如何做主从分离
    07-池化技术:如何减少频繁创建数据库连接的性能损耗
  • 原文地址:https://www.cnblogs.com/lcyfrog/p/11391899.html
Copyright © 2020-2023  润新知