BZOJ3224普通平衡树——非旋转treap

时间:2022-05-24 10:37:18

题目:

此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

n<=100000 所有数字均在-107到107内。

输入样例:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例:
106465
84185
492737

变量声明:size[x],以x为根节点的子树大小;ls[x],x的左儿子;rs[x],x的右子树;r[x],x节点的随机数;v[x],x节点的权值。

root,树的总根;tot,树的大小。

非旋转treap不同于旋转treap需要靠旋转来维护平衡树的性质,他的操作可以用简单暴力来形容——只有合并和断裂两个操作。他不但有treap的优良性质,还有许多优点:支持可持久化和区间操作,常数比splay小。

下面介绍一下非旋转treap的这两个操作:

1.断裂

就是去掉一条边,把treap拆分成两棵树,对于区间操作可以进行两次断裂来分割出一段区间再进行操作。

以查找value为例,从root往下走,如果v[x]>value,那么下一步走ls[x],之后的点都比x小,把x接到右树上,下一次再接到右树上的点就是x的左儿子。

v[x]<=value与上述类似,在这里不加赘述。

void split(int x,int &lroot,int &rroot,int val)
{
if(!x)
{
lroot=rroot=0;
return ;
}
if(v[x]<=val)
{
lroot=x;
split(rs[x],rs[lroot],rroot,val);
}
else
{
rroot=x;
split(ls[x],lroot,ls[rroot],val);
}
up(x);
}

2.合并

就是把断裂开的树合并起来,因为要维护堆的性质所以按可并堆来合并。

void merge(int &x,int a,int b)
{
if(!a||!b)
{
x=a+b;
return ;
}
if(r[a]<r[b])
{
x=a;
merge(rs[x],rs[a],b);
}
else
{
x=b;
merge(ls[x],a,ls[b]);
}
up(x);
}

为了方便删除,所以建议把相同权值的点分开来加入树中,不要都放在同一个点。

非旋转treap代码比较短(为了清晰我写的比较长qwq)。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
int INF=1000000000;
int n;
int opt,x;
int r[100010];
int ls[100010];
int rs[100010];
int size[100010];
int v[100010];
int root;
int tot;
void up(int x)
{
size[x]=size[ls[x]]+size[rs[x]]+1;
}
void build(int &x,int val)
{
tot++;
size[tot]=1;
r[tot]=rand();
v[tot]=val;
ls[tot]=rs[tot]=0;
x=tot;
}
void merge(int &x,int a,int b)
{
if(!a||!b)
{
x=a+b;
return ;
}
if(r[a]<r[b])
{
x=a;
merge(rs[x],rs[a],b);
}
else
{
x=b;
merge(ls[x],a,ls[b]);
}
up(x);
}
void split(int x,int &lroot,int &rroot,int val)
{
if(!x)
{
lroot=rroot=0;
return ;
}
if(v[x]<=val)
{
lroot=x;
split(rs[x],rs[lroot],rroot,val);
}
else
{
rroot=x;
split(ls[x],lroot,ls[rroot],val);
}
up(x);
}
void insert_sum(int val)
{
int x=0;
int y=0;
int z=0;
build(z,val);
split(root,x,y,val);
merge(x,x,z);
merge(root,x,y);
}
void delete_sum(int val)
{
int x=0;
int y=0;
int z=0;
split(root,x,y,val);
split(x,x,z,val-1);
merge(z,ls[z],rs[z]);
merge(x,x,z);
merge(root,x,y);
}
void ask_rank(int val)
{
int x=0;
int y=0;
split(root,x,y,val-1);
printf("%d\n",size[x]+1);
merge(root,x,y);
}
void ask_sum(int x,int num)
{
while(size[ls[x]]+1!=num)
{
if(num<=size[ls[x]])
{
x=ls[x];
}
else
{
num-=(size[ls[x]]+1);
x=rs[x];
}
}
printf("%d\n",v[x]);
}
void ask_front(int val)
{
int x=0;
int y=0;
split(root,x,y,val-1);
if(size[x]==0)
{
printf("0\n");
}
else
{
ask_sum(x,size[x]);
}
merge(root,x,y);
}
void ask_back(int val)
{
int x=0;
int y=0;
split(root,x,y,val);
ask_sum(y,1);
merge(root,x,y);
}
int main()
{
srand(16);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&opt,&x);
if(opt==1)
{
insert_sum(x);
}
else if(opt==2)
{
delete_sum(x);
}
else if(opt==3)
{
if(tot==0)
{
printf("0\n");
}
else
{
ask_rank(x);
}
}
else if(opt==4)
{
if(tot==0)
{
printf("0\n");
}
else
{
ask_sum(root,x);
}
}
else if(opt==5)
{
if(tot==0)
{
printf("0\n");
}
else
{
ask_front(x);
}
}
else if(opt==6)
{
if(tot==0)
{
printf("0\n");
}
else
{
ask_back(x);
}
}
}
return 0;
}