1.背景
由于我被那些转来转去的平衡树转得头昏脑涨,于是就想学学不需要旋转的无旋Treap(毕竟无旋Treap还可以可持久化,感觉挺棒),经过半个小时的理解和一个小时的码代码+调试,终于将例题AC..(不过常数好像有点大..是我原来写的替罪羊树的3倍)。
2.介绍
Treap,他的名称的由来就是Tree+heap,它是一颗同时具备二叉搜索树和堆的性质的一颗二叉树,并且他是通过rand一个数来维护保证树高为O(logn)的。它的两大核心操作就是Split和Merge,所有用它来完成的修改操作都需要通过这两大操作来完成。接下来就简要说说这两大操作。(P.S. 这下面的部分代码是有Pushdown函数的,不过如果你的操作中不会出现打标记的情况的话,是不用Pushdown操作的。)
2.1 Merge
Merge函数是将两个平衡树合并为一个平衡树,用法是int Merge(int a,int b),传入的是两颗需要合并的树的根,返回合并后的那棵树的根。但是有一点要注意的是,Merge两颗root分别为a,b的树需要有一个前提,tree(a)中最大的数要小于tree(b)中最小的数。之后操作其实非常简单,如果a点的rand值小于b点,那么就Merge(a.rs,b),然后将返回的值定为a的rs,否则就Merge(a,b.ls),将返回的值定为b的ls,这样可以很好地维护树高。
2.1.1 Code
inline int Merge(int a,int b) {
if(!a||!b)
return a|b;
Pushdown(a);
Pushdown(b);
if(tr[a].fix<tr[b].fix) {
tr[a].rs=Merge(tr[a].rs,b);
Pushup(a);
return a;
}
else {
tr[b].ls=Merge(a,tr[b].ls);
Pushup(b);
return b;
}
}
2.2 Split
Split函数是将一个平衡树分割成两个平衡树,用法是pair Split(int o,int k),这里传入的是需要Split的树的根和需要将前k个节点分离出去的k,返回的是分离后的两个平衡树的根。如果当前o的左子树的size恰好等于k,那么我们直接将左子树分离出去就行了;如果当前o的左子树的size+1恰好等于k,那么我们可以直接将右子树分离出去。否则就递归进行:如果左子树的size>k,那么递归到左子树继续分割,否则到右子树进行分割。不过在右子树分割的时候要注意递归时k应变成k-左子树size-1。并且注意Split完后要将分离掉的那边的孩子设为0。
2.2.1 Code(Split函数写得有点丑见谅qwq)
inline pair<int,int> Split(int o,int k) {
if(!o)
return make_pair(0,0);
Pushdown(o);
if(Sz(tr[o].ls)==k) {
int pre=tr[o].ls;
tr[o].ls=0;
Pushup(o);
return make_pair(pre,o);
}
if(Sz(tr[o].ls)+1==k) {
int pre=tr[o].rs;
tr[o].rs=0;
Pushup(o);
return make_pair(o,pre);
}
if(Sz(tr[o].ls)>k) {
pair<int,int>tmp=Split(tr[o].ls,k);
tr[o].ls=tmp.second;
Pushup(o);
return make_pair(tmp.first,o);
}
pair<int,int>tmp=Split(tr[o].rs,k-Sz(tr[o].ls)-1);
tr[o].rs=tmp.first;
Pushup(o);
return make_pair(o,tmp.second);
}
3.基本操作
介绍完两大核心操作后,接下来来讲讲如何利用这两个操作进行其他的操作。(P.S. 下文的cnt表示现在一共有cnt个节点)
3.0 一些函数的声明
//cnt为节点个数
//给每个节点赋值一个值来维护树
inline int Rand() {
return seed=(int)(1ll*seed*base%INT_MAX);
}
//节点回收(没有卡内存的话其实可以不用)
inline void Recycle(int x) {
rec[++rtp]=x;
}
struct Treap {
int sz,ls,rs,val,fix;
Treap(int _val=0):val(_val){ls=rs=sz=0;fix=Rand();}//需要建新节点的时候直接Treap(val)就行了
}tr[maxn];
//需要节点回收时使用。
inline void Clear(int o) {
if(!o)
return;
Clear(tr[o].ls);
Clear(tr[o].rs);
Recycle(o);
}
//有节点回收的Newnode
inline int Newnode(int val) {
int tmp=rtp?rec[rtp--]:++cnt;
tr[tmp]=Treap(val);
tr[tmp].sz=1;
return tmp;
}
//无节点回收的Newnode
inline int Newnode(int val) {
tr[++cnt]=Treap(val);
return cnt;
}
//判断这个节点是否是空节点,如果是空节点不需要上传。
inline int Sz(int o) {
return !o?0:tr[o].sz;
}
//更新子树sz和其他一些需要维护的东西
inline void Pushup(int o) {
tr[o].sz=Sz(tr[o].ls)+Sz(tr[o].rs)+1;
}
//给一个节点赋值
inline void Cover(int o,int x) {
tr[o].sum=x*tr[o].sz;
tr[o].val=x;tr[o].cov=x;
}
//翻转一个区间的两个孩子
inline void Revnode(int o) {
swap(tr[o].ls,tr[o].rs);
tr[o].rev^=1;
}
//Pushdown来下传标记(用区间反转标记和区间赋值标记来举例)
inline void Pushdown(int o) {
if(!o)
return;
if(tr[o].rev) {
if(tr[o].ls)
Revnode(tr[o].ls);
if(tr[o].rs)
Revnode(tr[o].rs);
tr[o].rev=0;
}
if(tr[o].cov!=-2333) {
if(tr[o].ls)
Cover(tr[o].ls,tr[o].cov);
if(tr[o].rs)
Cover(tr[o].rs,tr[o].cov);
tr[o].cov=-2333;
}
}
3.1 Insert
当我们想要插入一个值的时候,我们先查询这个值的rank,然后将原树分离成1~rank和rank+1~cnt两颗树,之后新建一个节点,再进行两次Merge将这个节点并入原来的节点中(root=Merge(Tree(1~rank),newnode),root=Merge(root,Tree(rank+1~cnt))),注意两次Merge完都要将root重新赋值。
3.1.1 Code
//这是插入在pos位后插入长度为k的一个序列
inline void Insert(int o,int pos,int k) {
Pushdown(o);
for(int i=1;i<=k;i++)
read(a[i]);
int now=Build(a,k);
pair<int,int>tmp=Split(o,pos);
rt=Merge(Merge(tmp.first,now),tmp.second);
}
//这是在第num位后插入一个数
inline void Insert(int o,int num) {
int rnk=Rank(o,num);
pair<int,int>tmp=Split(o,rnk);
tr[++cnt]=node(0,0,num,1);
rt=Merge(tmp.first,cnt);
rt=Merge(rt,tmp.second);
}
3.2 Delete
当我们想要删除一个值的时候,我们也是先查询这个值的rank,然后将原树分成3部分,1~rank-1,rank,rank+1~cnt,就是调用两次Split(pair tmp1=Split(root,rank),tmp2=Split(tmp1.first,rank-1)),之后我们再Merge(tmp2.first,tmp1.second)就行了,这样我们就能很好地将这个节点删除而不破坏原树的性质了。
3.2.1 Code
//关于Delete..如果题目卡内存的话,你需要Clear并回收节点(详细请看我的维护数列的那道题的Code)
inline void Delete(int o,int l,int r) {
Pushdown(o);
pair<int,int>tmp1=Split(o,r),tmp2=Split(tmp1.first,l-1);
Clear(tmp2.second);
rt=Merge(tmp2.first,tmp1.second);
}
3.3 Build
如果我们已经有了一个序列,然后需要将他插入到Treap中,那么我们先将这个序列单独建成一颗Treap之后再合并就行了,而这时候我们就需要一个叫做Build的函数来建树,在建树过程中,我们需要一个栈,栈中存着这个需要插入的序列中的已经被插入的节点,当我们有新节点y进入的时候,我们要在栈中找到第一个fix值小于这个节点的节点x(前面那些大于他的已经被pop出去了,注意在pop一个节点的时候我们需要将他Update一遍,更新节点信息),之后将新加的这个点作为x的右孩子,然后最后一个在栈中fix值大于节点y的fix值的节点就作为y的左孩子。再将节点y放到栈里就行了。最后返回的值为这个新树的根。
3.3.1 Code
inline int Build(int *v,int len) {
tp=0;
for(int i=1;i<=len;i++) {
int now=Newnode(v[i]),lst=0;
while(tp&&tr[stk[tp]].fix>tr[now].fix) {
Pushup(stk[tp]);
lst=stk[tp];
stk[tp--]=0;
}
if(tp)
tr[stk[tp]].rs=now;
tr[now].ls=lst;
stk[++tp]=now;
}
while(tp)
Pushup(stk[tp--]);
return stk[1];
}
3.4 Getkth&&Getrank&&prev(前驱)&&succ(后继)
这一部分的这4个操作,由于它是一颗平衡树,所以只需要暴力找就行了,reverse的话,打个标记然后pushdown就行了。
3.4.1 Code
//求前驱
inline int Prev(int o,int k) {
int res=-1e9;
while(o)
if(tr[o].val<k)
res=max(res,tr[o].val),o=tr[o].rs;
else
o=tr[o].ls;
return res;
}
//求后继
inline int Succ(int o,int k) {
int res=1e9;
while(o)
if(tr[o].val>k)
res=min(res,tr[o].val),o=tr[o].ls;
else
o=tr[o].rs;
return res;
}
//求一个数在Treap中的排名
inline int Rank(int o,int k) {
int res=0,t=1e9;
while(o) {
if(tr[o].val==k)
t=min(t,res+tr[tr[o].ls].sz+1);
if(tr[o].val<k)
res+=tr[tr[o].ls].sz+1,o=tr[o].rs;
else
o=tr[o].ls;
}
return t==1e9?res:t;
}
//求一个排名为k的数的值
inline int Num(int o,int k) {
while(1)
if(tr[tr[o].ls].sz+1==k)
return tr[o].val;
else if(tr[tr[o].ls].sz+1<k)
k-=tr[tr[o].ls].sz+1,o=tr[o].rs;
else
o=tr[o].ls;
}
3.5 区间操作:Reverse(翻转)&&Cover(赋值)
对于所有的区间操作,用无旋Treap写都是非常简单的,你只需要通过两次Split分离出这个l~r的区间,然后给这个区间打上标记就行了(不要忘记Merge回去)。
3.5.1 Code
//区间赋值
inline void Makesame(int o,int l,int r,int k) {
Pushdown(o);
pair<int,int>tmp1=Split(o,r),tmp2=Split(tmp1.first,l-1);
Cover(tmp2.second,k);
rt=Merge(Merge(tmp2.first,tmp2.second),tmp1.second);
}
//区间反转
inline void Reverse(int o,int l,int r) {
Pushdown(o);
pair<int,int>tmp1=Split(o,r),tmp2=Split(tmp1.first,l-1);
Revnode(tmp2.second);
rt=Merge(Merge(tmp2.first,tmp2.second),tmp1.second);
}
3.6 敬请期待..
还有一些操作我还不知道,等我会了之后再更新吧。
4.例题
4.1 [BZOJ3224]普通平衡树
4.1.1 思路
这题..就是个模板题吧,没什么好说的,直接扔模板就好辣!
4.1.2 Code
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool Finish_read;
template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;}
template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');}
template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');}
template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);}
/*================Header Template==============*/
const int base=48271,maxn=100005;
int rt,seed=233,cnt;
inline int Rand() {
return seed=(int)(1ll*seed*base%INT_MAX);
}
struct node {
int ls,rs,fix,val,sz;
node(int _ls=0,int _rs=0,int _val=0,int _sz=0):ls(_ls),rs(_rs),val(_val),sz(_sz){fix=Rand();}
}tr[maxn];
inline void update(int o) {
tr[o].sz=tr[tr[o].ls].sz+tr[tr[o].rs].sz+1;
}
inline int Merge(int a,int b) {
if(!a||!b)
return a|b;
if(tr[a].fix<tr[b].fix) {
tr[a].rs=Merge(tr[a].rs,b);
update(a);
return a;
}
else {
tr[b].ls=Merge(a,tr[b].ls);
update(b);
return b;
}
}
inline pair<int,int> Split(int o,int k) {
if(!o)
return make_pair(0,0);
if(tr[tr[o].ls].sz==k) {
int pre=tr[o].ls;
tr[o].ls=0;
update(o);
return make_pair(pre,o);
}
if(tr[tr[o].ls].sz+1==k) {
int pre=tr[o].rs;
tr[o].rs=0;
update(o);
return make_pair(o,pre);
}
if(tr[tr[o].ls].sz>k) {
pair<int,int> tmp=Split(tr[o].ls,k);
tr[o].ls=tmp.second;
update(o);
return make_pair(tmp.first,o);
}
pair<int,int> tmp=Split(tr[o].rs,k-tr[tr[o].ls].sz-1);
tr[o].rs=tmp.first;
update(o);
return make_pair(o,tmp.second);
}
inline int Prev(int o,int k) {
int res=-1e9;
while(o)
if(tr[o].val<k)
res=max(res,tr[o].val),o=tr[o].rs;
else
o=tr[o].ls;
return res;
}
inline int Succ(int o,int k) {
int res=1e9;
while(o)
if(tr[o].val>k)
res=min(res,tr[o].val),o=tr[o].ls;
else
o=tr[o].rs;
return res;
}
inline int Rank(int o,int k) {
int res=0,t=1e9;
while(o) {
if(tr[o].val==k)
t=min(t,res+tr[tr[o].ls].sz+1);
if(tr[o].val<k)
res+=tr[tr[o].ls].sz+1,o=tr[o].rs;
else
o=tr[o].ls;
}
return t==1e9?res:t;
}
inline int Num(int o,int k) {
while(1)
if(tr[tr[o].ls].sz+1==k)
return tr[o].val;
else if(tr[tr[o].ls].sz+1<k)
k-=tr[tr[o].ls].sz+1,o=tr[o].rs;
else
o=tr[o].ls;
}
inline void Insert(int o,int num) {
int rnk=Rank(o,num);
pair<int,int>tmp=Split(o,rnk);
tr[++cnt]=node(0,0,num,1);
rt=Merge(tmp.first,cnt);
rt=Merge(rt,tmp.second);
}
inline void Delete(int o,int num) {
int rnk=Rank(o,num);
pair<int,int>tmp1=Split(o,rnk),tmp2=Split(tmp1.first,rnk-1);
rt=Merge(tmp2.first,tmp1.second);
}
int main() {
int q;
read(q);
while(q--) {
int op,x;
read(op);read(x);
if(op==1)
Insert(rt,x);
if(op==2)
Delete(rt,x);
if(op==3)
printf("%d\n",Rank(rt,x));
if(op==4)
printf("%d\n",Num(rt,x));
if(op==5)
printf("%d\n",Prev(rt,x));
if(op==6)
printf("%d\n",Succ(rt,x));
}
}