【洛谷P3369】【模板】普通平衡树题解
题目链接
题意:
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
输入格式:
第一行为n,表示操作的个数。下面n行每行有两个整数opt和x,opt表示操作的序号
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
样例输入:
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
样例输出:
106465 84185 492737
时空限制:
每个测试点1s,128MB
数据范围:
n≤105
|x|≤107
1≤opt≤6
题解:
首先来讲讲什么是Treap……
Treap的中文名叫树堆(Tree+Heap),同时拥有权值和优先级;对于权值来说,它是一棵二叉搜索树(BST);对于优先级来说,它是一个堆。它可以被当作平衡树来用。
我们希望平衡树的高度尽量小,因为只有这样才能使每次操作的均摊时间复杂度趋于O(logn)。为了避免极端数据使平衡树退化为链,导致时间复杂度退化为O(n),我们可以想到用优先级来维护Treap的形态使它的高度尽量趋于平衡,因为当每个节点的权值和优先级确定时,Treap的形态是唯一确定的。我们把每个节点的优先级用随机数rand()来赋值,这样Treap就趋于平衡了。
详细代码如下(指针实现):
1 #include<cstdio> 2 #include<ctime> 3 #include<cstdlib> 4 struct node{ 5 node*ch[2];//指向左(ch[0])右(ch[1])子节点的指针 6 int v;//权值 7 int r;//优先级 8 int cnt;//该节点数的个数(数可以重复) 9 int size;//以该节点为根的子树中数的个数 10 node(int vv){//新建节点的构造函数 11 ch[0]=ch[1]=NULL;//两指针初始化为空指针 12 v=vv;//权值初始化 13 r=rand();//优先级用随机数初始化 14 cnt=size=1;//只含一个数 15 } 16 }; 17 node*super;//指向整棵Treap的大根的指针 18 int n,ans; 19 inline void maintain(node*p){//维护该节点的附加信息 20 p->size=p->cnt; 21 if(p->ch[0]!=NULL)p->size+=p->ch[0]->size; 22 if(p->ch[1]!=NULL)p->size+=p->ch[1]->size; 23 } 24 inline void rot(node*&p,int lr){//旋转操作(lr==0:左旋,lr==1:右旋) 25 node*k=p->ch[lr^1]; 26 p->ch[lr^1]=k->ch[lr]; 27 k->ch[lr]=p; 28 p=k; 29 } 30 void ins(node*&p,int val){//插入操作:在以p为根的子树中插入一个val数 31 if(p==NULL){//空节点直接建新点 32 p=new node(val); 33 }else if(val==p->v){//找到了已经存在的节点 34 p->cnt++; 35 p->size++; 36 }else if(val<p->v){ 37 ins(p->ch[0],val);//在左子节点中插入 38 if(p->ch[0]->r<p->r){//维护优先级(小根堆) 39 rot(p,1); 40 maintain(p->ch[1]); 41 } 42 maintain(p); 43 }else{ 44 ins(p->ch[1],val);//在右子节点中插入 45 if(p->ch[1]->r<p->r){ 46 rot(p,0); 47 maintain(p->ch[0]); 48 } 49 maintain(p); 50 } 51 } 52 void del(node*&p,int val){//删除操作:在以p为根的子树中删除一个val数 53 if(val<p->v){ 54 del(p->ch[0],val); 55 maintain(p); 56 }else if(val>p->v){ 57 del(p->ch[1],val); 58 maintain(p); 59 }else{ 60 if(p->cnt>1){//该节点包含多个数,直接减即可 61 p->cnt--; 62 p->size--; 63 }else{//删除该节点 64 if(p->ch[0]!=NULL){ 65 if(p->ch[1]!=NULL){//该节点有左右子节点 66 int lr=p->ch[0]->r<p->ch[1]->r?0:1;//选优先级小的子节点为根(小根堆) 67 rot(p,lr^1);//旋转 68 del(p->ch[lr^1],val);//递归向下删除 69 maintain(p);//递归返回向上维护 70 }else{//无右子节点 71 node*pp=p; 72 p=p->ch[0]; 73 delete pp; 74 } 75 }else{//无左子节点 76 if(p->ch[1]!=NULL){ 77 node*pp=p; 78 p=p->ch[1]; 79 delete pp; 80 }else{//都无 81 delete p; 82 p=NULL; 83 } 84 } 85 } 86 } 87 } 88 int qrank(node*p,int val){//当前以p为根的子树中小于val的数的个数+1 89 int ssl=p->ch[0]==NULL?0:p->ch[0]->size;//该子树中小于val的数的个数 90 if(val==p->v)return ssl+1; 91 else if(val<p->v){ 92 if(p->ch[0]!=NULL)return qrank(p->ch[0],val); 93 else return 1; 94 }else{ 95 if(p->ch[1]!=NULL)return ssl+p->cnt+qrank(p->ch[1],val); 96 else return p->size+1; 97 } 98 } 99 int qval(node*p,int rank){//在当前子树中查找排名为rank的数 100 int ssl=p->ch[0]==NULL?0:p->ch[0]->size; 101 if(rank<=ssl)return qval(p->ch[0],rank); 102 else if(rank<=ssl+p->cnt)return p->v; 103 else return qval(p->ch[1],rank-ssl-p->cnt); 104 } 105 void pred(node*p,int val){//在当前子树中查找val的前驱 106 if(val<=p->v){ 107 if(p->ch[0]!=NULL)pred(p->ch[0],val); 108 }else{ 109 ans=p->v;//ans为暂定的答案,当ans无法再修改时,ans为最终答案 110 if(p->ch[1]!=NULL)pred(p->ch[1],val); 111 } 112 } 113 void succ(node*p,int val){ 114 if(val>=p->v){ 115 if(p->ch[1]!=NULL)succ(p->ch[1],val); 116 }else{ 117 ans=p->v; 118 if(p->ch[0]!=NULL)succ(p->ch[0],val); 119 } 120 } 121 int main(){ 122 srand(time(0));//初始化随机数种子 123 scanf("%d",&n); 124 while(n--){ 125 int opt,x; 126 scanf("%d%d",&opt,&x); 127 switch(opt){ 128 case 1:{ 129 ins(super,x); 130 break; 131 } 132 case 2:{ 133 del(super,x); 134 break; 135 } 136 case 3:{ 137 printf("%d\n",qrank(super,x)); 138 break; 139 } 140 case 4:{ 141 printf("%d\n",qval(super,x)); 142 break; 143 } 144 case 5:{ 145 pred(super,x); 146 printf("%d\n",ans); 147 break; 148 } 149 case 6:{ 150 succ(super,x); 151 printf("%d\n",ans); 152 break; 153 } 154 } 155 } 156 return 0; 157 }