神仙数据结构——FHQ_Treap
$FHQ\_Treap$是平衡树的一种,它不仅支持几乎所有的平衡树的操作,而且实现特别简单,总共只有两个操作。这里来简单介绍一下。
基本操作
$FHQ\_Treap$和$Treap$一样是需要用随机值来维护树的形态的,但是$FHQ\_Treap$不需要旋转来调整形态,而是用$Split$和$Merge$来实现,也就是分离与合并,也就是这两种操作,完成了$FHQ\_Treap$的所有操作。
分离(Split)
把一棵平衡树分离成两棵,从而方便插入和删除。分离有两种做法,一种是按权值分离,一种是按子树大小分离,根据情况选择。
权值分离版:
void split(int rt,int k,int &x,int &y) { if( !rt ) x=y=0; else { if( val[rt]<=k ) x=rt, split(ch[rt][1],k,ch[rt][1],y); else y=rt, split(ch[rt][0],k,x,ch[rt][0]); pushup(rt); } }
子树大小版:
void split(int rt,int k,int &x,int &y) { if( !rt ) x=0, y=0; else { pushdown(rt); if( siz[ch[rt][0]]<k ) { x=rt, split(ch[rt][1],k-siz[ch[rt][0]]-1,ch[rt][1],y); } else { y=rt, split(ch[rt][0],k,x,ch[rt][0]); } pushup(rt); } }
合并(merge)
合并操作与左偏树的合并类似,按照优先级合并,把优先级高的作为父节点。当然,合并的时候,函数中的两个参数$x,y$要按照顺序,$x$是权值较小的,$y$是权值较大的,因为合并的时候是按照随机值来确定优先级的。(当然在函数中交换也行,不过常数大些)
int merge(int x,int y) { if( !x || !y ) return x+y; if( p[x]<p[y] ) { ch[x][1]=merge(ch[x][1],y); pushup(x); return x; } else { ch[y][0]=merge(x,ch[y][0]); pushup(y); return y; } }
平衡树的其他操作的实现
插入新节点(insert)
先把树按照新结点的权值分离,然后再把新结点当作一棵树与两个分离出来的树合并。
inline int neo(int v) { val[++tot]=v; siz[tot]=1; p[tot]=rand(); return tot; } inline void insert(int v) { int x,y; split(root,v,x,y); root=merge(merge(x,neo(v)),y); }
删除节点(delete)
按删除节点的权值$v$把树分离成小于$v$,等于$v$,大于$v$三段,然后去掉等于$v$的树的根节点(也就是直接合并根节点的左右子树),然后合并回来。
inline void delet(int v) { int x,y,z; split(root,v,x,y); split(x,v-1,x,z); z=merge(ch[z][0],ch[z][1]); root=merge(x,merge(z,y)); }
查询权值为$k$的节点排名
按照权值分离然后输出左子树大小$+1$即可。
inline void kth(int k) { int x,y; split(root,k-1,x,y); printf("%d\n",siz[x]+1); root=merge(x,y); }
查询$k$小值
和其他平衡树一样搜索、递归。
int rank(int rt,int k) { if( siz[ch[rt][0]]==k-1 ) return val[rt]; if( siz[ch[rt][0]]>=k ) return rank(ch[rt][0],k); return rank(ch[rt][1],k-siz[ch[rt][0]]-1); }
查询前驱后继
按权值分离然后查询分离出的子树的最大/最小值就行了。
inline void prev(int v) { int x,y; split(root,v-1,x,y); printf("%d\n",rank(x,siz[x])); root=merge(x,y); } inline void nex(int v) { int x,y; split(root,v,x,y); printf("%d\n",rank(y,1)); root=merge(x,y); }
可以看出,$FHQ\_Treap$的操作都非常简短,仅仅用了两种核心操作就实现了其他平衡树的能实现的全部功能,非常方便。在绝大多数情况下需要使用平衡树的时候都可以直接用$FHQ\_Treap$,既容易理解又方便实现,而且也很容易$Debug$。
同时,$FHQ\_Treap$也是支持可持久化的,不过这里就不再赘述(我会告诉你其实我也没写过?)。
完整模板
//It is made by HolseLee on 27th Sep 2018 //Luogu.org P3369 #include<bits/stdc++.h> using namespace std; const int N=1e5+7; int n,root,tot; int val[N],ch[N][2],p[N],siz[N]; inline int read() { char ch=getchar(); int num=0; bool flag=false; while( ch<'0' || ch>'9' ) { if( ch=='-' ) flag=true; ch=getchar(); } while( ch>='0' && ch<='9' ) { num=num*10+ch-'0'; ch=getchar(); } return flag ? -num : num; } inline void pushup(int rt) { siz[rt]=siz[ch[rt][0]]+siz[ch[rt][1]]+1; } void split(int rt,int k,int &x,int &y) { if( !rt ) x=y=0; else { if( val[rt]<=k ) x=rt, split(ch[rt][1],k,ch[rt][1],y); else y=rt, split(ch[rt][0],k,x,ch[rt][0]); pushup(rt); } } int merge(int x,int y) { if( !x || !y ) return x+y; if( p[x]<p[y] ) { ch[x][1]=merge(ch[x][1],y); pushup(x); return x; } else { ch[y][0]=merge(x,ch[y][0]); pushup(y); return y; } } inline int neo(int v) { val[++tot]=v; siz[tot]=1; p[tot]=rand(); return tot; } inline void insert(int v) { int x,y; split(root,v,x,y); root=merge(merge(x,neo(v)),y); } inline void delet(int v) { int x,y,z; split(root,v,x,y); split(x,v-1,x,z); z=merge(ch[z][0],ch[z][1]); root=merge(x,merge(z,y)); } inline void kth(int k) { int x,y; split(root,k-1,x,y); printf("%d\n",siz[x]+1); root=merge(x,y); } int rank(int rt,int k) { if( siz[ch[rt][0]]==k-1 ) return val[rt]; if( siz[ch[rt][0]]>=k ) return rank(ch[rt][0],k); return rank(ch[rt][1],k-siz[ch[rt][0]]-1); } inline void prev(int v) { int x,y; split(root,v-1,x,y); printf("%d\n",rank(x,siz[x])); root=merge(x,y); } inline void nex(int v) { int x,y; split(root,v,x,y); printf("%d\n",rank(y,1)); root=merge(x,y); } int main() { srand(19260817); n=read(); int opt; for(int i=1; i<=n; ++i) { opt=read(); if( opt==1 ) insert(read()); else if( opt==2 ) delet(read()); else if( opt==3 ) kth(read()); else if( opt==4 ) printf("%d\n",rank(root,read())); else if( opt==5 ) prev(read()); else nex(read()); } return 0; }
如需转载,请署名作者并附上原文链接,蒟蒻非常感激
名称:HolseLee
博客地址:www.cnblogs.com/cytus
个人邮箱:1073133650@qq.com