洛谷P3369 【模板】普通平衡树 treap
网址:https://www.luogu.org/problem/P3369
题意:
编写一个数据结构在每次$O(logn)(1 \leq n \leq 1e6)$完成以下功能:
一、插入一个数到序列中;二、在序列中删除某一个数;三、找到第$k$大;四、询问第$k$大的数;五、找到$x$的前驱,六、找到$x$的后继。
题解:
很显然,二叉搜索树就可以完成这个任务,不过最坏情况下,二叉树会退化成链,就会超时,因此我们就需要平衡二叉树,其中较为容易编写,速度又快的是$treap$。$treap$分为有旋$treap$和无旋$treap$,前者速度快,但不支持持久化,后者速度慢,但支持持久化。
一、有旋treap
treap的节点的权值维护了二叉树的性质,为了平衡,就需要通过另外一个数维护堆性质使其平衡,这个数就是每一个节点对应的随机数。旋转的时候按照随机数进行旋转,分为左旋和右旋,如图:
(参考博客:https://blog.csdn.net/K346K346/article/details/50808879)
左旋和右旋
显然,右旋就是原树根的左子树变成树根,然后原树根的左子树的右子树变成原树根的左子树,左旋同理。易证旋转后二叉树性质不变。
插入时,先找到插入点,然后回溯时旋转。
删除较为复杂,删除时,先找到删除点,然后观察子树的随机数值选择一个子树作为删除后的树根,然后通过旋转把需要删除的节点移动到叶子后删除。
查询操作易于理解,看代码即可。
AC代码:
#include <bits/stdc++.h> using namespace std; const int MAXN=100005; const int inf=0x3f3f3f3f; struct Treap { struct node { int val, rnd, lc, rc, size, num; }; int cnt=0; node tr[MAXN]; void init() { cnt=0; } int _rand() { static int seed=12345; return seed=(int)seed*482711LL%2147483647; } void pushup(int p) { tr[p].size=tr[tr[p].lc].size+tr[tr[p].rc].size+tr[p].num; } void right(int &k) { int tmp=tr[k].lc; tr[k].lc=tr[tmp].rc; tr[tmp].rc=k; tr[tmp].size=tr[k].size; pushup(k); k=tmp; } void left(int &k) { int tmp=tr[k].rc; tr[k].rc=tr[tmp].lc; tr[tmp].lc=k; tr[tmp].size=tr[k].size; pushup(k); k=tmp; } void insert(int &p,int x) { if(p==0) { p=++cnt; tr[p].val=x; tr[p].num=tr[p].size=1; tr[p].lc=tr[p].rc=0; tr[p].rnd=_rand(); return; } ++tr[p].size; if(x==tr[p].val) ++tr[p].num; else if(x<tr[p].val) { insert(tr[p].lc,x); if(tr[tr[p].lc].rnd<tr[p].rnd) right(p); } else if(x>tr[p].val) { insert(tr[p].rc,x); if(tr[tr[p].rc].rnd<tr[p].rnd) left(p); } } void del(int &p,int x) { if(p==0) return; if(tr[p].val==x) { if(tr[p].num>1) --tr[p].num,--tr[p].size; else { if(tr[p].lc==0||tr[p].rc==0) p=tr[p].lc+tr[p].rc; else if(tr[tr[p].lc].rnd<tr[tr[p].rc].rnd) right(p),del(p,x); else if(tr[tr[p].lc].rnd>tr[tr[p].rc].rnd) left(p),del(p,x); } } else if(tr[p].val<x) --tr[p].size,del(tr[p].rc,x); else --tr[p].size,del(tr[p].lc,x); } int queryrnk(int &p,int x) { if(p==0) return 0; else if(tr[p].val==x) return tr[tr[p].lc].size; else if(tr[p].val<x) return tr[tr[p].lc].size+tr[p].num+queryrnk(tr[p].rc,x); else return queryrnk(tr[p].lc,x); } int querynum(int &p,int rnk) { if(p==0) return 0; if(tr[tr[p].lc].size>=rnk) return querynum(tr[p].lc,rnk); rnk-=tr[tr[p].lc].size; if(rnk<=tr[p].num) return tr[p].val; rnk-=tr[p].num; return querynum(tr[p].rc,rnk); } int queryfront(int &p,int x) { if(p==0) return -inf; if(tr[p].val<x) return max(tr[p].val,queryfront(tr[p].rc,x)); else if(tr[p].val>=x) return queryfront(tr[p].lc,x); } int queryback(int &p,int x) { if(p==0) return inf; if(tr[p].val>x) return min(tr[p].val,queryback(tr[p].lc,x)); else if(tr[p].val<=x) return queryback(tr[p].rc,x); } }; int pos; Treap tr; int main() { int n; scanf("%d",&n); int m,k; tr.init(); for(int i=0;i<n;++i) { scanf("%d%d",&m,&k); if(m==1) tr.insert(pos,k); else if(m==2) tr.del(pos,k); else if(m==3) printf("%d\n",tr.queryrnk(pos,k)+1); else if(m==4) printf("%d\n",tr.querynum(pos,k)); else if(m==5) printf("%d\n",tr.queryfront(pos,k)); else if(m==6) printf("%d\n",tr.queryback(pos,k)); } return 0; }
二、无旋treap
无旋$treap$的核心操作是平衡树的合并和分裂,合并时,先对权值比较,然后保证
#include <bits/stdc++.h> using namespace std; const int MAXN=1e5+5; struct Treap { struct node { int lc,rc,val,rnk,size; }; node tr[MAXN]; int cnt=0; void init() { cnt=0; } void pushup(int p) { tr[p].size=tr[tr[p].lc].size+tr[tr[p].rc].size+1; } int _rand() { static int seed=int(12345); return seed = int(seed * 482711ll % 2147483647); } void merge(int &rt,int a,int b)//保证a的权值小于b的权值 { if(a==0||b==0) { rt=a+b; return; } if(tr[a].rnk<tr[b].rnk) rt=a,merge(tr[rt].rc,tr[a].rc,b); else rt=b,merge(tr[rt].lc,a,tr[b].lc); pushup(rt); } void split(int rt,int &a,int &b,int val) { if(rt==0) { a=b=0; return; } if(tr[rt].val<=val) a=rt,split(tr[rt].rc,tr[a].rc,b,val); else b=rt,split(tr[rt].lc,a,tr[b].lc,val); pushup(rt); } int newnode(int val) { int rt=++cnt; tr[rt].size=1; tr[rt].val=val; tr[rt].lc=tr[rt].rc=0; tr[rt].rnk=_rand(); return rt; } void insert(int &rt,int val) { int x=0,y=0,n=newnode(val); split(rt,x,y,val); merge(x,x,n); merge(rt,x,y); } void del(int &rt,int val) { int x=0,y=0,z=0; split(rt,x,y,val); split(x,x,z,val-1); merge(z,tr[z].lc,tr[z].rc); merge(x,x,z); merge(rt,x,y); } int querynum(int rt,int rnk) { while(tr[tr[rt].lc].size+1!=rnk) { if(tr[tr[rt].lc].size<rnk) rnk-=tr[tr[rt].lc].size+1,rt=tr[rt].rc; else rt=tr[rt].lc; } return tr[rt].val; } int queryrnk(int &rt,int val) { int x=0,y=0; split(rt,x,y,val-1); int tmp=tr[x].size; merge(rt,x,y); return tmp; } int queryfront(int &rt,int val) { int x=0,y=0; split(rt,x,y,val-1); int tmp=querynum(x,tr[x].size); merge(rt,x,y); return tmp; } int queryback(int &rt,int val) { int x=0,y=0; split(rt,x,y,val); int tmp=querynum(y,1); merge(rt,x,y); return tmp; } }; Treap tr; int p; int main() { int n; scanf("%d",&n); int m,k; tr.init(); /*tr.newnode(0x3f3f3f3f); tr.cnt=1; tr.tr[1].size=0; p=1;*/ for(int i=0;i<n;++i) { scanf("%d%d",&m,&k); if(m==1) tr.insert(p,k); else if(m==2) tr.del(p,k); else if(m==3) printf("%d\n",tr.queryrnk(p,k)+1); else if(m==4) printf("%d\n",tr.querynum(p,k)); else if(m==5) printf("%d\n",tr.queryfront(p,k)); else if(m==6) printf("%d\n",tr.queryback(p,k)); } return 0; }