Luogu 3369 普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
- 求x的后继(后继定义为大于x,且最小的数)
输入输出格式
输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号( 1≤opt≤6 1 \leq opt \leq 6 1≤opt≤6 )
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
输入输出样例
输入样例#1:
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598输出样例#1:
106465 84185 492737说明
时空限制:1000ms,128M
1.n的数据范围: n≤100000 n \leq 100000 n≤100000
2.每个数的数据范围: [−107,107][-{10}^7, {10}^7][−107,107]
来源:Tyvj1728 原名:普通平衡树
在此鸣谢
01trie拟或成为最大赢家(虽然说空间有点那啥。。。不过时间复杂度是严格log的。。。)。。。
01trie是个神奇的东西。。。
将每个数字二进制拆分后(需要先加上1e7使得都是非负整数)从高位到地位插入到trie中(只保存01节点),然后查询的话YY一下就行(或者看代码)。
01trie十分好写!!!01trie十分好写!!!01trie十分好写!!!
如果说split-merge treap是时间换代码的话(事实上那个我觉得不太好理解。。。而且一般split用的pair速度超慢的样子。。。(不过clj的那种不需要保存优先值的方法挺好的。。。)),01trie就是空间换代码(然而只是32倍的空间!而且是最烂的情况下!)。。。
不过美中不足的是,01trie只能用于非负整数(也就是说其它的数据(比如说实数)需要先离散化后才能用。。。)。。。
(然而事实上考试的时候都是强制在线吧。。。)
不过一般写平衡树就是整数了吧。。。于是01trie还是很实用的样子。。。╰(*´︶`*)╯
(看起来的确很短)
1 #include <cstdio> 2 const int N = 100010 * 33; 3 int n, x, y, ch[N][2], num[N], tot, root = ++ tot; 4 #define walk for(int i = 31, rt = root, t ; ~i ; i --) 5 #define cond(cd, st) if(x == cd) st; 6 void ins(int val, int c) { 7 val += (int)1e7; 8 walk { 9 if(!ch[rt][t = val >> i & 1]) ch[rt][t] = ++ tot; 10 num[rt = ch[rt][t]] += c; 11 } 12 } 13 int rak(int val, int ret = 0, int t = 0) { 14 val += (int)1e7; 15 walk { 16 if((t = val >> i & 1)) ret += num[ch[rt][0]]; 17 rt = ch[rt][t]; 18 } 19 return ret; 20 } 21 int kth(int k, int ret = 0) { 22 walk 23 if(k > num[ch[rt][0]]) ret |= 1 << i, k -= num[ch[rt][0]], rt = ch[rt][1]; 24 else rt = ch[rt][0]; 25 return ret - (int)1e7; 26 } 27 int main() { 28 scanf("%d", &n); 29 while(n --) { 30 scanf("%d%d", &x, &y); 31 cond(1, ins(y, 1)); 32 cond(2, ins(y, -1)); 33 cond(3, printf("%d\n", rak(y) + 1)); 34 cond(4, printf("%d\n", kth(y))); 35 cond(5, printf("%d\n", kth(rak(y)))); 36 cond(6, printf("%d\n", kth(rak(y + 1) + 1))); 37 } 38 }
你以为这就完了?不。
刚才在写某篇博文(博客里置顶的那个入门到入殓)的时候突然想起来数组模拟指针有一种优美的写法~~~
1 #include <cstdio> 2 const int N = 100010 * 33; 3 int n, x, y, ch[N][2], num[N], tot, root = ++ tot; 4 #define walk for(int i = 31, rt = root, t ; ~i ; i --) 5 #define cond(cd, st) if(x == cd) st; 6 void ins(int val, int c) { 7 val += (int)1e7; 8 walk { 9 if(!rt[ch][t = val >> i & 1]) rt[ch][t] = ++ tot; 10 (rt = rt[ch][t])[num] += c; 11 } 12 } 13 int rak(int val, int ret = 0, int t = 0) { 14 val += (int)1e7; 15 walk { 16 if((t = val >> i & 1)) ret += rt[ch][0][num]; 17 rt = rt[ch][t]; 18 } 19 return ret; 20 } 21 int kth(int k, int ret = 0) { 22 walk 23 if(k > rt[ch][0][num]) ret |= 1 << i, k -= rt[ch][0][num], rt = rt[ch][1]; 24 else rt = rt[ch][0]; 25 return ret - (int)1e7; 26 } 27 int main() { 28 scanf("%d", &n); 29 while(n --) { 30 scanf("%d%d", &x, &y); 31 cond(1, ins(y, 1)); 32 cond(2, ins(y, -1)); 33 cond(3, printf("%d\n", rak(y) + 1)); 34 cond(4, printf("%d\n", kth(y))); 35 cond(5, printf("%d\n", kth(rak(y)))); 36 cond(6, printf("%d\n", kth(rak(y + 1) + 1))); 37 } 38 }