说在前面
搁了很久的坑,终于遇到题目可以学一发。
听说这玩意除了LCT,几乎完爆同是维护平衡树的splay,好像LCT也可以用treap实现
Treap
顾名思义就是Tree+Heap,即树堆,既有二叉查找树的性质也有堆的性质,通常利用中序遍历的顺序和堆的高度为
l
o
g
log
log来解决许多问题,属于平衡树的一类。
本质上就是笛卡尔树,每个点有两个权值
v
a
l
,
k
e
y
val,key
val,key,
v
a
l
val
val即为要排序的权值,
k
e
y
key
key为随机权值,用来维护堆的性质,由于权值随机,这里建的小根堆高度为
l
o
g
log
log,所以在Treap上所有路径操作和子树操作时间都是
O
(
l
o
g
)
O(log)
O(log)。
非旋Treap
相比一般的旋转Treap多了非旋两字,即Treap是静态的,因此可以支持可持久化操作和其它骚操作。
非旋Treap有两个核心操作:分裂split和合并merge。
split(x,k):表示将
x
x
x这棵Treap分裂成中序遍历前k的和剩余的两棵Treap,返回这两棵Treap的根。是一个递归操作,分裂时保持了Treap的性质。
pr split(int x,int k){//sz为子树大小,pr为pair<int,int>
if(!x) return pr(0,0);
pr y;
if(sz[tr[x].l]>=k){
y=split(tr[x].l,k);
tr[x].l=y.second,update(x);
y.second=x;
}
else{
y=split(tr[x].r,k-sz[tr[x].l]-1);
tr[x].r=y.first,update(x);
y.first=x;
}
return y;
}
merge(x,y):基于随机权值的合并,使得操作后的Treap树高仍为 l o g log log。同样是一个递归过程。
int merge(int x,int y){//tr[x].k即为随机权值key
if(!x || !y) return x^y;
if(tr[x].k<tr[y].k) return tr[x].r=merge(tr[x].r,y),update(x),x;
else return tr[y].l=merge(x,tr[y].l),update(y),y;
}
有了split和merge,我们可以实现很多操作,例如插入:找到Treap上插入点 x x x的排名k,把Treap裂成前k的Treap x _x x和剩余的Treap y _y y,依次合并Treap x _x x, x x x,Treap y _y y。删除类似,对于序列区间操作可以类比。
代码十分简短,常数远小于均摊复杂度的splay。
例题(模板):BZOJ3224
多了几个操作,注意找排名时是要找最小的即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
using namespace std;
const int N=1e5+10,inf=2147483647;
typedef pair<int,int> pr;
struct treap{
int l,r,v,k;
}tr[N];
int rt;
int sz[N];
void update(int x){
sz[x]=sz[tr[x].l]+sz[tr[x].r]+1;
}
int tot=0;
int merge(int x,int y){
if(!x || !y) return x^y;
if(tr[x].k<tr[y].k) return tr[x].r=merge(tr[x].r,y),update(x),x;
else return tr[y].l=merge(x,tr[y].l),update(y),y;
}
pr split(int x,int k){
if(!x) return pr(0,0);
pr y;
if(sz[tr[x].l]>=k){
y=split(tr[x].l,k);
tr[x].l=y.second,update(x);
y.second=x;
}
else{
y=split(tr[x].r,k-sz[tr[x].l]-1);
tr[x].r=y.first,update(x);
y.first=x;
}
return y;
}
int find(int k){
pr x=split(rt,k-1),y=split(x.second,1);
int t=y.first;
rt=merge(merge(x.first,t),y.second);
return tr[t].v;
}
int rank(int x,int v){
int tmp=inf,t=0;
while(x){
if(v==tr[x].v) tmp=min(tmp,t+sz[tr[x].l]+1);
if(v>tr[x].v) t+=sz[tr[x].l]+1,x=tr[x].r;
else x=tr[x].l;
}
return tmp==inf?t:tmp;
}
void insert(int v){
int k=rank(rt,v);
pr x=split(rt,k);
sz[++tot]=1,tr[tot].v=v,tr[tot].k=rand();
rt=merge(merge(x.first,tot),x.second);
}
void del(int v){
int k=rank(rt,v);
pr x=split(rt,k-1),y=split(x.second,1);
rt=merge(x.first,y.second);
}
int next(int v){
int x=rt,tmp=inf;
while(x) tr[x].v>v?(tmp=min(tmp,tr[x].v),x=tr[x].l):x=tr[x].r;
return tmp;
}
int last(int v){
int x=rt,tmp=-inf;
while(x) tr[x].v<v?(tmp=max(tmp,tr[x].v),x=tr[x].r):x=tr[x].l;
return tmp;
}
int main()
{
int n;
scanf("%d",&n);
while(n--){
int op,x;
scanf("%d %d",&op,&x);
if(op==1) insert(x);
else if(op==2) del(x);
else if(op==3) printf("%d\n",rank(rt,x));
else if(op==4) printf("%d\n",find(x));
else if(op==5) printf("%d\n",last(x));
else printf("%d\n",next(x));
}
}