題面:


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 Input101 1064654 11 3177211 4609291 6449851 841851 898516 819681 4927375 493598Sample Output10646584185492737HINT1.n的數據范圍:n<=1000002.每個數的數據范圍:[-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 }
?
?
?