平衡树1:无旋treap
无旋 treap
其实早就学了,但是忘了。既然复习了,顺便写一个笔记,下次遗忘时能够快速回忆吧。
简介
前置:平衡树定义、堆。
treap 是一种基于随机化实现的弱平衡平衡树,通过对原二叉搜索树的每一个点赋一个随机点权 \(key\),并使 \(key\) 满足堆的形式,来控制原二叉搜索树的子孙关系,达成期望 \(O(logn)\) 的深度。无旋 treap 是 treap 的一个版本,基于分裂和合并。
操作(与基础函数们)
其中,分裂(split)、合并(merge)和查找(find)为基础操作。其余操作可以由这三者完成。
分裂(split)
这里的分裂统一为按值(\(val\))分裂,按照排名分裂之类可以由基本操作完成,具体请看后面。
首先,确认我们的函数大致结构:我们要做的就是类似于可持久化数据结构一样,不断将原 treap 的子树连到分裂后的两颗 treap 上。
考虑目前考虑到了根节点为 \(root\),分裂后的两个 treap 在 \(root\) 的子树内的“预设”根节点(或者说需要移植的左/右子树)为 \(lf\) 和 \(rg\)。
假设一颗这颗 treap 的根节点要被分到左边(具体根据排名、值或者其他的,由你的 check 判断),则将 \(lg\) 赋值为 \(root\)(即类似可持久化数据结构一样直接用 \(root\) 的信息),然后把 \(root\) 的右子树给断开,然后去右子树上递归。其中 \(lf\) 变为现在 \(lf\) 的右子树(即原本 \(root\) 的右子树指针),\(rg\) 不变。
根节点在右边的情况同理。
void split(int root,int k,int &lf,int &rg)//拆分
{
if(!root) lf=rg=0;
else
{
if(t[root].val<=k) lf=root,split(t[root].rc,k,t[root].rc,rg);
else rg=root,split(t[root].lc,k,lf,t[root].lc);
update(root);
}
}
合并(merge)
……想不到什么要写的了。就是简单的模拟堆的合并即可。别把二叉搜索树的左右子树弄反了就行。
int merge(int x,int y)//合并
{
if(!x||!y) return x+y;
if(t[x].key<t[y].key)
{
t[x].rc=merge(t[x].rc,y);
update(x);
return x;
}
else
{
t[y].lc=merge(x,t[y].lc);
update(y);
return y;
}
}
查询指定排名的 \(val\)(find)
类似的,直接递归即可,十分显然。由于众所周知的递归大常数,这里写成循环。
int find(int root,int k)//查找第 x 个数
{
while(1)
{
if(k<=t[t[root].lc].siz)
{
root=t[root].lc;
}
else if(k==t[t[root].lc].siz+1)
{
return root;
}
else
{
k-=t[t[root].lc].siz+1;
root=t[root].rc;
}
}
}
至此,三个基础函数介绍完毕。
插入(insert)
先根据要插入的值找到将它插在哪里,然后根据那个地方 split,然后按顺序 merge 起来即可。
void insert(int a,int id,int &root)
{
int x,y;
split(root,a,x,y);
root=merge(merge(x,New(id,a)),y);
}
删除(del)
和插入类似,将插入操作反过来即可。
具体的,先把拆成三部分——左边的,要删的和右边的,再把左边的和右边的拼起来即可。类似的,如果想要区间删除也可以,这里写的是单点删除。
void del(int a,int &root)
{
int x,y,z;
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(t[y].lc,t[y].rc);
root=merge(merge(x,y),z);
}
排名、数值间的转化
如果是给定排名,就跑一遍 find 函数找到其数值即可。
给定数值,就把这个数值对应的节点 split 出来,然后查询其 \(siz\) 即可。
例题(P3369)
维护如下 \(6\) 种操作:
- 插入一个数 \(a\)。
- 删除一个数 \(a\)(若有多个相同的应只删除一个,保证存在至少一个)。
- 查询小于 \(a\) 的数的数量并加 \(1\)。
- 查询第 \(a\) 个大的数。
- 查 \(a\) 的前驱,保证答案存在。
- 查 \(a\) 的后继,保证答案存在。
其中,前驱/后继定义为小于/大于 \(a\) 的最大/小的数。
除了已经给出具体函数的前四种操作,剩下的两种操作该怎样维护呢?
由于二者本质相同,接下来以操作五为例,讲解一下排名、数值转化的具体应用。
操作五
首先,二叉搜索树的中序遍历模拟的是一个序列,并且从小到大排序。那么其前驱的数值恰为最靠前的 \(a\) 前面的数,于是其前驱的排名恰为 \(a\) 的排名减一。
于是,不难想到,先把 \(a\) 转化为排名,然后将其减一,再转化为数值,最后就能得到结果啦。
tp.split(root,a-1,x,y);
printf("%d\n",tp.t[tp.find(x,tp.t[x].siz)].val);
root=tp.merge(x,y);
代码
整道题代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct Node{
int id,val,key,siz;
int fa,lc,rc;
Node(){
fa=lc=rc=0;
id=val;
key=rand();
siz=1;
}
};
struct Treap{
Node t[N];
int size;
void update(int root)
{
t[root].siz=1+t[t[root].lc].siz+t[t[root].rc].siz;
}
void split(int root,int k,int &lf,int &rg)//拆分
{
if(!root) lf=rg=0;
else
{
if(t[root].val<=k) lf=root,split(t[root].rc,k,t[root].rc,rg);
else rg=root,split(t[root].lc,k,lf,t[root].lc);
update(root);
}
}
int merge(int x,int y)//合并
{
if(!x||!y) return x+y;
if(t[x].key<t[y].key)
{
t[x].rc=merge(t[x].rc,y);
update(x);
return x;
}
else
{
t[y].lc=merge(x,t[y].lc);
update(y);
return y;
}
}
int find(int root,int k)//查找第 x 个数
{
while(1)
{
if(k<=t[t[root].lc].siz)
{
root=t[root].lc;
}
else if(k==t[t[root].lc].siz+1)
{
return root;
}
else
{
k-=t[t[root].lc].siz+1;
root=t[root].rc;
}
}
}
int New(int id,int val)
{
t[++size].key=rand();
t[size].id=id;
t[size].val=val;
return size;
}
void insert(int a,int id,int &root)
{
int x,y;
split(root,a,x,y);
root=merge(merge(x,New(id,a)),y);
}
void del(int a,int &root)
{
int x,y,z;
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(t[y].lc,t[y].rc);
root=merge(merge(x,y),z);
}
}tp;
int n,m,ans;
int main()
{
srand((unsigned)time(NULL));
scanf("%d",&n);
int root=0;
tp.t[root].siz=0;
for(int i=1;i<=n;i++)
{
int opt,a;
scanf("%d %d",&opt,&a);
int x,y;
if(opt==1) tp.insert(a,i,root);//插入 a
else if(opt==2) tp.del(a,root);//删除 a
else if(opt==3)//查询小于 a 的数的数量并加 1
{
tp.split(root,a-1,x,y);
printf("%d\n",tp.t[x].siz+1);
root=tp.merge(x,y);
}
else if(opt==4) printf("%d\n",tp.t[tp.find(root,a)].val);//查询第 a 个大的数
else if(opt==5)//查 a 的前驱,**保证答案存在**
{
tp.split(root,a-1,x,y);
printf("%d\n",tp.t[tp.find(x,tp.t[x].siz)].val);
root=tp.merge(x,y);
}
else//查 a 的后继,**保证答案存在**
{
tp.split(root,a,x,y);
printf("%d\n",tp.t[tp.find(y,1)].val);
root=tp.merge(x,y);
}
}
return 0;
}

浙公网安备 33010602011771号