非旋转Treap(fhq_treap)树学习笔记

格式不大好看,我日后会更新,加上可持久化以及美化一下

非旋转$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)就是把两颗根节点分别为abtreap树合并为一颗树,合并后的树还满足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 splitint 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的节点等:

插入权值a的节点:

Split(root,a,x,y);  //在以root为根的树中,分裂成小于等于a和大于a的两颗树

Root=merge(merge(x,newnode(a)),y);  //xay合并成一颗树

删除权值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的三个合并

查询a的排名

Split(root,a-1,x,y);  //分裂成小于a和大于等于a

K=siz[x]+1;        //小于a的个数+1就是a的排名

Root=Merge(x,y);  //重新合并恢复原树

查找排名为k的节点

Int kthint xint 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 tagpushdownpushupupdate的操作与序列的其他模型完全一致。

 

【例一、 普通平衡树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的序列,每个序列的元素是一个整数(废话)。要支持以下三种操作:

  1. [L,R][L,R]这个区间内的所有数加上V
  2. [L,R][L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1
  3. [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;

}

 

可持久化的treapFHQ Treap是可持久化的,在mergesplit的时候,将所有更改的数据都重建一份备份,就完成了可持久化的访问记录,每个root都是一个全新的版本。

转载于:https://www.cnblogs.com/JerryZheng2005/p/10371089.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值