「BZOJ3224」JoyOI 1728 普通平衡树
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
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
Sample Output
84185
492737
题解
这是一道平衡树的模板题
treap是一棵修改了结点顺序的二叉查找树
通常树内的每个结点x都有一个关键字值key[x],另外,还要为结点分配一个随机数。
假设所有的优先级是不同的,所有的关键字也是不同的。treap的结点排列成让关键字遵循二叉查找树性质,并且优先级遵
循最小堆顺序性质:
1.如果v是u的左孩子,则key[v] < key[u].
2.如果v是u的右孩子,则key[v] > key[u].
3.如果v是u的孩子,则rand[u] > rand[u].
这两个性质的结合就是为什么这种树被称为“treap”的原因,因为它同时具有二叉查找树和heap的特征。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; struct data{ int l,r,v,size,rnd,w; }tr[100005]; int n,size,root,ans; void update(int k)//更新结点信息 { tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w; } void rturn(int &k) { int t=tr[k].l;tr[k].l=tr[t].r;tr[t].r=k; tr[t].size=tr[k].size;update(k);k=t; } void lturn(int &k) { int t=tr[k].r;tr[k].r=tr[t].l;tr[t].l=k; tr[t].size=tr[k].size;update(k);k=t; } void insert(int &k,int x) { if(k==0) { size++;k=size; tr[k].size=tr[k].w=1;tr[k].v=x;tr[k].rnd=rand(); return; } tr[k].size++; if(tr[k].v==x)tr[k].w++;//每个结点顺便记录下与该节点相同值的数的个数 else if(x>tr[k].v) { insert(tr[k].r,x); if(tr[tr[k].r].rnd<tr[k].rnd)lturn(k);//维护堆性质 } else { insert(tr[k].l,x); if(tr[tr[k].l].rnd<tr[k].rnd)rturn(k); } } void del(int &k,int x) { if(k==0)return; if(tr[k].v==x) { if(tr[k].w>1) { tr[k].w--;tr[k].size--;return;//若不止相同值的个数有多个,删去一个 } if(tr[k].l*tr[k].r==0)k=tr[k].l+tr[k].r;//有一个儿子为空 else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd) rturn(k),del(k,x); else lturn(k),del(k,x); } else if(x>tr[k].v) tr[k].size--,del(tr[k].r,x); else tr[k].size--,del(tr[k].l,x); } int query_rank(int k,int x) { if(k==0)return 0; if(tr[k].v==x)return tr[tr[k].l].size+1; else if(x>tr[k].v) return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r,x); else return query_rank(tr[k].l,x); } int query_num(int k,int x) { if(k==0)return 0; if(x<=tr[tr[k].l].size) return query_num(tr[k].l,x); else if(x>tr[tr[k].l].size+tr[k].w) return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w); else return tr[k].v; } void query_pro(int k,int x) { if(k==0)return; if(tr[k].v<x) { ans=k;query_pro(tr[k].r,x); } else query_pro(tr[k].l,x); } void query_sub(int k,int x) { if(k==0)return; if(tr[k].v>x) { ans=k;query_sub(tr[k].l,x); } else query_sub(tr[k].r,x); } int main() { scanf("%d",&n); int opt,x; for(int i=1;i<=n;i++) { scanf("%d%d",&opt,&x); switch(opt) { case 1:insert(root,x);break; case 2:del(root,x);break; case 3:printf("%d\n",query_rank(root,x));break; case 4:printf("%d\n",query_num(root,x));break; case 5:ans=0;query_pro(root,x);printf("%d\n",tr[ans].v);break; case 6:ans=0;query_sub(root,x);printf("%d\n",tr[ans].v);break; } } return 0; } |
8.23:用vector可以AC,但vector的插入理论上是On的
实际测试发现,vector从头插入10W个数我的电脑只用了0.8s
倒数50亿用了12s,干脆我们把插入视为根号级别。。。
所以说,vector还是很不错的,用来写暴力没准出奇迹呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<set> #include<vector> #include<algorithm> #define inf 1000000000 using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n; vector<int> a; void insert(int x) { a.insert(upper_bound(a.begin(),a.end(),x),x); return; } void del(int x) { a.erase(lower_bound(a.begin(),a.end(),x)); return; } int find(int x) { return lower_bound(a.begin(),a.end(),x)-a.begin()+1; } int main() { n=read(); a.reserve(200000); int f,x; for(int i=1;i<=n;i++) { f=read();x=read(); switch(f) { case 1:insert(x);break; case 2:del(x);break; case 3:printf("%d\n",find(x));break; case 4:printf("%d\n",a[x-1]);break; case 5:printf("%d\n",*--lower_bound(a.begin(),a.end(),x));break; case 6:printf("%d\n",*upper_bound(a.begin(),a.end(),x));break; } } return 0; } |
” 3.如果v是u的孩子,则rand[u] > rand[u].”
这里是不是说错了?应该是 rand[u]> rand[v] 吧。
请问一下…平衡树插入结点那里,如果是放到左孩子的话,为什么是左孩子的rnd小于这个点的rnd才右旋…感觉应该是大于…
[…] http://hzwer.com/1712.html […]
请问一下,黄学长,你的treap删除一个点时,当它两个儿子都不为空时,并没有把左儿子赋到它上面啊。。。。
旋转时赋的?
set 如果要重载字符 要重载哪个?
小于号吧