【模板】可持久化平衡树(非旋Treap)

洛谷传送门:【模板】可持久化平衡树


题意

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作(对于各个以往的历史版本):

1.插入x数

2.删除x数(若有多个相同的数,因只删除一个,如果没有请忽略该操作)

3.查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

4.查询排名为x的数

5.求x的前驱(前驱定义为小于x,且最大的数,如不存在输出-2147483647)

6.求x的后继(后继定义为大于x,且最小的数,如不存在输出2147483647)
和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本。(操作3, 4, 5, 6即保持原版本无变化)
每个版本的编号即为操作的序号(版本0即为初始状态,空树)


数据范围

1n5105 1 ≤ n ≤ 5 ⋅ 10 5 , 109xi109 − 10 9 ≤ x i ≤ 10 9


题解

模板。详见代码。
很好的讲解:无旋Treap——从入门到放弃


代码

#include
#include
#include
#include
#include
using namespace std;
const int N=5e5+10;
int n,rt[N],cnt;
//rt-->root 记录每一次操作建的树的根节点,cnt 结点个数

struct Node{
    int ch[2];
    int rnd,sz,v;
    //rnd 随机数 sz->size 子结点(包括自身)的个数 v 结点的值
}t[N*50];
//不断的开点,所以空间要开够

inline int read()
{
    int x=0,t=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*t;
}

inline int copynode(int x)
{
    t[++cnt]=t[x];
    return cnt;
}
//复制节点
inline int newnode(int x)
{
   t[++cnt].v=x;t[cnt].sz=1;t[cnt].rnd=rand();
   return cnt;
}
//开点
inline void update(int k)
{
    if(k)
    t[k].sz=t[t[k].ch[0]].sz+t[t[k].ch[1]].sz+1;
}

inline void split(int now,int k,int &x,int &y)//取址动态修改
{
    if(!now) {x=0;y=0;return;}//当分裂到最底层,意味已经彻底将两棵树分裂了,停止
    if(t[now].v<=k){//分裂点在now的右儿子上
        x=copynode(now);//将x设为根
        split(t[x].ch[1],k,t[x].ch[1],y);//继续分裂
    }else{
        y=copynode(now);
        split(t[y].ch[0],k,x,t[y].ch[0]);
    }
    update(x);update(y);//递归返回时,每次要update更新子结点个数
}
//将以now为树根的树分成以x为根和以y为根的两棵树,x中最大值为k,y中最小值大于k
inline int merge(int x,int y)
{
    if(!x || !y) return x+y;//返回不为零的值
    if(t[x].rnd//根据Treap的性质,取随机数合并
        int z=copynode(x);//开点
        t[z].ch[1]=merge(t[z].ch[1],y);
        update(z);//将新得到的root结点返回
        return z;
    }else{
        int z=copynode(y);
        t[z].ch[0]=merge(x,t[z].ch[0]); 
        update(z);
        return z;
    }
}
//将分别以x,y为根的两颗树合并
inline void insert(int now,int k)
{
    int x=0,y=0,z=0;
    split(rt[now],k,x,y);//找到k应在的位置
    z=newnode(k);//给插入的数建立一个新结点
    rt[now]=merge(merge(x,z),y);//合并复原
}

inline void del(int now,int k)
{
    int x=0,y=0,z=0;
    split(rt[now],k,x,y);//先把大于k的结点分离
    split(x,k-1,x,z);//保证z中只有值为k的结点
    z=merge(t[z].ch[0],t[z].ch[1]);//若值为k的结点有多个,则合并后少一个,如果原本只有一个或者没有,则返回0,相当于删除了值为k的结点
    rt[now]=merge(merge(x,z),y);//合并复原
}

inline int rnk(int now,int k)
{
    int x=0,y=0;
    split(rt[now],k-1,x,y);//以x为根的树中包含了所有值小于k的结点。
    return t[x].sz+1;//结点数+1就是k的排名
}

inline int kth(int x,int k)
{
   while(1){
      if(t[t[x].ch[0]].sz+1 ==k) return t[x].v;
      else if(t[t[x].ch[0]].sz>=k) x=t[x].ch[0];
      else {k-=(t[t[x].ch[0]].sz+1);x=t[x].ch[1];}
      //1-> cause the same are not on the same
   }
}
//找到排名为k的结点
inline int pre(int now,int k)
{
   int x=0,y=0,z=0;
   split(rt[now],k-1,x,y);
   if(!x) return -2147483647;
   return kth(x,t[x].sz);//以x为根的树中值最大的即为k的前驱    
}

inline int suf(int now,int k)
{
    int x=0,y=0,z=0;
    split(rt[now],k,x,y);
    if(!y) return 2147483647;
    return kth(y,1);//以y为根的树中值最小的即为k的后继
}

int main(){
    srand(471);//随机数种子,可不写
    n=read();
    for(int i=1;i<=n;i++){
        int in=read(),op=read();
        rt[i]=rt[in];//每次操作都新开了一个点,所以基于历史操作,直接将本次操作的rt赋值为历史版本的rt
        if(op==1) insert(i,read());
        else 
        if(op==2) del(i,read());
        else 
        if(op==3) printf("%d\n",rnk(i,read()));
        else 
        if(op==4) printf("%d\n",kth(rt[i],read()));
        else 
        if(op==5) printf("%d\n",pre(i,read()));
        else  
        printf("%d\n",suf(i,read()));
    }
    return 0;
}

你可能感兴趣的:(【模板】可持久化平衡树(非旋Treap))