格式不大好看,我日后会更新,加上可持久化以及美化一下
非旋转$Treap$树($fhqtreap$)是不需要旋转的平衡树,仅使用分裂合并,一样可以保持平衡树的性质,并且可以非常简单地处理区间问题。
无旋$treap$的节点定义和$treap$一样,都要同时满足树性质和堆性质,我们还是用$rand()$来实现平衡。每个非旋转$treap$和$treap$一样,是一个二叉排序平衡树,他满足二叉平衡树的所有特点。$Treap$树主要有两种典型应用,排序序列和非排序序列。
对于排序序列,定义为序列$A[i]$是一个有排序意义的序列,输入时是无序的,在树中按平衡树的方式安排序列的顺序,也就是在$treap$的任何时刻,$treap$的中序遍历是满足排序的,这是平衡树的基本特点。
$Treap$有其基本的元素:$v[u]$代表这个节点的值,$key[u]=rand()$是这个节点的随机优先值($key$值),$lson[u]$为左子树,$rson[u]$为右子树,$size[u]$为节点的子树大小。对于$treap$的每个节点,其左子树的值都小于该节点,其右子树的值都大于该节点;同时节点的$key$值大于左右子树。非旋转的$treap$树一定满足这个定义。
$Root$为$treap$的根,一个空的$treap$,其根为$root=0$;同时我们把$0$作为哨兵节点,表示这个节点无效,相当于NULL。
对于排序的$fhqtreap$来说只有两个基本操作$split$和$merge$。
l int merge(int a,int b)就是把两颗根节点分别为a和b的treap树合并为一颗树,合并后的树还满足treap的所有定义和特性。(当然a树比b树小!!)
int merge(int a,int b){
if(!a||!b) return x=a+b; //当被合并的树有一颗为空树是,直接返回非空的树
if(key[a]<key[b]) {pushdown(a);rson[a]=merge(rson[a],b);pushup(a);return a;}
else{pushdown(b);lson[b]=merge(a,lson[b]);pushup(b);return b;}
}
l Void split(int x,int &a,int &b,int v)就是把x中分裂出小于等于val的和大于val的两颗树,分裂开的树分别都是满足treap的所有定义和特性的树。(a树比b树小!!)
Void split(int x,int &a,int &b,int v){
If(x==0) a=b=0; Else{
Pushdown(x);
If(val[x]<=v) a=x,return split(rson[x],rson[x],y,v); //v在右子树中
Else b=x,return split(lson[x],x,lson[x],v); //v在左子树中
pushup(x);
}
}
对于一般权值平衡树的操作,有:插入、删除、查找a的排名、查找排名为k的节点等:
l 插入权值a的节点:
Split(root,a,x,y); //在以root为根的树中,分裂成小于等于a和大于a的两颗树
Root=merge(merge(x,newnode(a)),y); //x,a,y合并成一颗树
l 删除权值a的节点
Split(root,a,x,z); //分裂成小于等于a和大于a的两颗树
Split(x,a-1,x,y); //把小于等于a的树分裂成小于a和等于a的两棵树
Y=merge(lson[y],rson[y]); //在等于a的树y中,删除一个等于的,重新合并
Root=merge(merge(x,y),z); //小于a的、等于a的,大于a的三个合并
l 查询a的排名
Split(root,a-1,x,y); //分裂成小于a和大于等于a的
K=siz[x]+1; //小于a的个数+1就是a的排名
Root=Merge(x,y); //重新合并恢复原树
l 查找排名为k的节点
Int kth(int x,int k){
If(k==siz[lson[x]]+1) return x; //x正好是第k名,返回
Return k<=siz[lson[x]]? kth(lson[x],k):kth(rson[x],k- siz[lson[x]]-1); //第k名在左还是右
}
FHQ treap在序列问题上的应用,在实际上,能用splay求解的序列问题,同样可以用treap解决,在序列问题上,序列是按其下标排列的,下标就是平衡树中的排名,在这里,lazy tag、pushdown、pushup、update的操作与序列的其他模型完全一致。
【例一、 普通平衡树luogu3369】
在这个例子中,我们实际上就是使用了treap在普通权值平衡树上的应用,要求实现插入一个元素a,删除元素a,求第k大的元素,求元素a的排名,元素a的前驱和后驱。模板程序:
Int lson[N],rson[N],siz[N],val[N],rnd[N],root,tot=0;
Int newnode(int v){ val[++tot]=v,lson[tot]=rson[tot]=0,siz[tot]=1,rnd[tot]=rand(); return tot;}
Void pushup(int x){siz[x]=lson[x]+rson[x]+1;}
Int merge(int x,int y){
If(x==0||y==0) return x+y;
If(rnd[x]<rnd[y]) {rson[x]=merge(rson[x],y);pushup(x);return x;}
Else {lson[y]=merge(x,lson[y]);pushup(y);return y;}
}
Void split(int r,int &x,int &y,int a){
If(r==0) x=y=0; else{
If(a<val[x]) y=r,split(lson[r],x,lson[r],a),pushup(y);
Else x=r,split(rson[r],rson[r],y,a),pushup(x);}
}
Int kth(int x,int k){
If(k==siz[lson[x]]+1) return x;
If(k<=siz[lson[x]]) return kth(lson[x],k); else return kth(rson[x],k-siz[lson[x]]-1);
}
Int main(){
Int T,op,a,x,y,z; scanf(“%d”,&T);
Srand((unsigned int)time(NULL));
While(T--){
Scanf(“%d%d”,&op,&a);
If(op==1) split(root,x,y,a),root=merge(merge(x,newnode(a)),y);
Else if(op==2) split(root,x,y,a),split(x,x,y,a-1),y=merge(lson[y],rson[y]),
root=merge(merge(x,y),z);
else if(op==3) split(root,x,y,a-1),printf(“%d\n”,siz[x]+1),root=merge(x,y);
else if(op==4) printf(“%d\n”,val[kth(root,a)]);
else if(op==5) split(root,x,y,a-1),printf(“%d\n”,val[kth(x,siz[x])]),root=merge(x,y);
else if(op==6) split(root,x,y,a),printf(“%d\n”,val[kth(y,1)]),root=merge(x,y);
}
Return 0;
}
【例二、 序列终结者luogu P4146】
给定一个长度为N的序列,每个序列的元素是一个整数(废话)。要支持以下三种操作:
- 将[L,R][L,R]这个区间内的所有数加上V
- 将[L,R][L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1。
- 求[L,R][L,R]这个区间中的最大值。
最开始所有元素都是0
题解:这是一个序列的模板题,虽然没有插入和删除,但是有区间修改、区间翻转、区间最大值的查询,可以用SPLAY求解,但我们这里用FHQ Treap求解。
注意:当用FHQ Treap求解序列的问题时,这是一颗序列平衡树,而不是权值平衡树,当处理的排序非权值时,split不是根据val分裂,而是根据siz分裂,也就是根据kth顺序分裂。
int val[N],siz[N],rnd[N],lson[N],rson[N],mx[N],rev[N],lazy[N];
int n,m,root,tot,cnt=0;
inline int newnode(int a){val[++tot]=a,siz[tot]=1,rnd[tot]=rand(),mx[tot]=a;return tot;}
inline void update(int r){
siz[r]=siz[lson[r]]+siz[rson[r]]+1;
mx[r]=val[r];
if(lson[r]) mx[r]=max(mx[r],mx[lson[r]]);
if(rson[r]) mx[r]=max(mx[r],mx[rson[r]]);
}
inline void addone(int x,int d){if(x) val[x]+=d,lazy[x]+=d,mx[x]+=d;}
inline void revone(int x){if(x) rev[x]^=1,swap(lson[x],rson[x]);}
inline void pushdown(int r){
if(lazy[r]) addone(lson[r],lazy[r]),addone(rson[r],lazy[r]);
if(rev[r]) revone(lson[r]),revone(rson[r]);
lazy[r]=rev[r]=0;
}
int merge(int x,int y){
if(x==0||y==0) return x+y;
if(rnd[x]<rnd[y]) {pushdown(x),rson[x]=merge(rson[x],y),update(x);return x;}
pushdown(y),lson[y]=merge(x,lson[y]),update(y);return y;
}
void split(int r,int &x,int &y,int k){
if(r==0) {x=y=0; return; }
if(k<=siz[lson[r]]) y=r,pushdown(r),split(lson[r],x,lson[r],k),update(r);
else x=r,pushdown(r),split(rson[r],rson[r],y,k-siz[lson[r]]-1),update(r);
}
void modify(int x,int y,int v){
int a,b,c;
split(root,a,c,y),split(a,a,b,x-1);
addone(b,v);
root=merge(merge(a,b),c);
}
void reverse(int x,int y){
int a,b,c;
split(root,a,c,y),split(a,a,b,x-1);
revone(b);
root=merge(merge(a,b),c);
}
int query(int x,int y){
int a,b,c,res;
split(root,a,c,y),split(a,a,b,x-1);
res=mx[b];
root=merge(merge(a,b),c);
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) root=merge(root,newnode(0));
while(m--){
int op,x,y,v;
scanf("%d%d%d",&op,&x,&y);
if(op==1){ scanf("%d",&v); modify(x,y,v);
}else if (op==2) reverse(x,y);
else if(op==3) printf("%d\n",query(x,y));
}
return 0;
}
可持久化的treap,FHQ Treap是可持久化的,在merge和split的时候,将所有更改的数据都重建一份备份,就完成了可持久化的访问记录,每个root都是一个全新的版本。