bzoj3224 Tyvj 1728 普通平衡树题解--Treap
题面:
Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 1. 插入x数 2. 删除x数(若有多个相同的数,因只删除一个) 3. 查询x数的排名(若有多个相同的数,因输出最小的排名) 4. 查询排名为x的数 5. 求x的前驱(前驱定义为小于x,且最大的数) 6. 求x的后继(后继定义为大于x,且最小的数) Input 第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6) Output 对于操作3,4,5,6每行输出一个数,表示对应答案 Sample Input 10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598 Sample Output 106465 84185 492737 HINT 1.n的数据范围:n<=100000 2.每个数的数据范围:[-2e9,2e9]
题解:
今天第一次接触平衡二叉树的概念,做了一道模版题,觉得Treap这东西很神奇啊~
顾名思义,Treap=heap+tree,就是把堆和二叉树结合在了一起。
但是为什么不用一般的二叉树呢?(为了给我们增大代码量)不对,是因为普通树原来是log(n)级别的,但是经过各种insert啊,del啊什么的就可能失去“平衡”,节点集中在一侧什么的,结果成了一条长链,这就把log(n)的算法变成O(n)级的线性了。
而现在的Treap就是解决这个问题的方法之一。
Treap中的节点在满足树的性质(左儿子都小,右儿子都大之类的)的同时,还对每个节点加入了一个“优先级”,并将节点按照堆的性质排序。这里的优先级采用随机生成的方式,所以节点的左右分布是随机的,以此保证整棵树的相对平衡。
那我们怎么样对这些节点进行堆的排序呢?
这就有一个看起来很厉害的操作了--旋转。
旋转分为左旋和右旋。当我们某个节点的左儿子优先度大于本节点,就需要进行右旋,右儿子大,就左旋。
二叉左旋
一棵二叉平衡树的子树,根是Root,左子树是x,右子树的根为RootR,右子树的两个孩子树分别为RLeftChild和RRightChild。则左旋后,该子树的根为RootR,右子树为RRightChild,左子树的根为Root,Root的两个孩子树分别为x(左)和RLeftChild(右)。
二叉右旋
一棵二叉平衡树的子树,根是Root,右子树是x,左子树的根为RootL,左子树的两个孩子树分别为LLeftChild和LRightChild。则右旋后,该子树的根为RootL,左子树为LLeftChild,右子树的根为Root,Root的两个孩子树分别为LRightChild(左)和x(右)。
来自百度百科,个人觉得挺容易懂的。
那么问题来了,为什么你这么一转,还能保持树的性质成立呢?为什么不会把节点权小的和大的弄返呢?不会把树弄乱吗?
这就是旋转的真正厉害之处了----可以发现,旋转前后,该子树的中序遍历是不变的!就是说并不会改变数列的大小顺序。我也不是很懂具体是为什么能这样,但是确实很厉害。
剩下就没什么了,树嘛,插入删除的都比较基础。
放代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=100010,inf=100000000; 4 int cnt,ret,n,t1,t2,root; 5 struct treap{ 6 int lc,rc,key,pri,siz,val; 7 /*key是关键字(权),pri是优先度,siz为子树大小,val表示key这个数有几个*/ 8 }a[maxn]; 9 void pushup(int &o){ 10 a[o].siz=a[a[o].lc].siz+a[a[o].rc].siz+a[o].val; 11 return; 12 } 13 void lturn(int &o){ 14 int t=a[o].rc; 15 a[o].rc=a[t].lc; 16 a[t].lc=o; 17 a[t].siz=a[o].siz; 18 pushup(o); 19 o=t; 20 return; 21 } 22 void rturn(int &o){ 23 int t=a[o].lc; 24 a[o].lc=a[t].rc; 25 a[t].rc=o; 26 a[t].siz=a[o].siz; 27 pushup(o); 28 o=t; 29 return; 30 } 31 void insert(int &o,int t){ 32 if(!o){ 33 o=++cnt; 34 a[o]=(treap){0,0,t,rand(),1,1};//rand()随机一个优先度 35 return; 36 } 37 a[o].siz++; 38 if(t==a[o].key)a[o].val++; 39 else if(t<a[o].key){ 40 insert(a[o].lc,t); 41 if(a[a[o].lc].pri>a[o].pri)rturn(o); 42 } 43 else{ 44 insert(a[o].rc,t); 45 if(a[a[o].rc].pri>a[o].pri)lturn(o);// 46 } 47 return; 48 } 49 void del(int &o,int k){ 50 if(!o)return; 51 if(k==a[o].key){ 52 if(a[o].val>1){ 53 a[o].val--; 54 a[o].siz--; 55 } 56 else if(!(a[o].lc*a[o].rc)){//如果左右只有一个儿子 57 o=a[o].lc+a[o].rc; 58 } 59 else if(a[a[o].lc].pri<a[a[o].rc].pri){ 60 lturn(o); 61 del(o,k); 62 } 63 else{ 64 rturn(o); 65 del(o,k); 66 } 67 } 68 else if(k<a[o].key) 69 { 70 --a[o].siz; 71 del(a[o].lc,k); 72 } 73 else 74 { 75 --a[o].siz; 76 del(a[o].rc,k); 77 } 78 return; 79 } 80 int query_rank(int o,int k){ 81 if(!o)return 0; 82 if(k<a[o].key)return query_rank(a[o].lc,k); 83 if(k==a[o].key)return a[a[o].lc].siz+1; 84 return a[a[o].lc].siz+a[o].val+query_rank(a[o].rc,k); 85 } 86 int query_num(int o,int k){ 87 if(!o)return 0; 88 if(k<=a[a[o].lc].siz)return query_num(a[o].lc,k); 89 if(k<=a[a[o].lc].siz+a[o].val)return a[o].key; 90 return query_num(a[o].rc,k-a[a[o].lc].siz-a[o].val); 91 } 92 void query_pre(int o,int k){ 93 if(!o)return; 94 if(k<=a[o].key)query_pre(a[o].lc,k); 95 else{ 96 ret=a[o].key; 97 query_pre(a[o].rc,k); 98 } 99 return; 100 } 101 void query_pos(int o,int k){ 102 if(!o)return; 103 if(k>=a[o].key)query_pos(a[o].rc,k); 104 else{ 105 ret=a[o].key; 106 query_pos(a[o].lc,k); 107 } 108 return; 109 } 110 int main(){ 111 scanf("%d",&n); 112 srand(n); 113 for(int i=1;i<=n;i++){ 114 scanf("%d%d",&t1,&t2); 115 if(t1==1)insert(root,t2); 116 else if(t1==2)del(root,t2); 117 else if(t1==3)printf("%d\n",query_rank(root,t2)); 118 else if(t1==4)printf("%d\n",query_num(root,t2)); 119 else if(t1==5){ 120 ret=-inf; 121 query_pre(root,t2); 122 printf("%d\n",ret); 123 } 124 else if(t1==6){ 125 ret=inf; 126 query_pos(root,t2); 127 printf("%d\n",ret); 128 } 129 } 130 return 0; 131 }