【模板】Treap
看了一上午才看明白,Treap = Tree + Heap,是棵弱平衡树。
一棵树同时具有二叉搜索树的性质和堆的性质,可以完成二叉搜索树和堆的操作。
有人曰:
Treap代码量小,速度较快
不能分裂合并,可以可持久化
挺好的东西。
于是我就学了。
可完成的操作:1.插入x
2.删除x数(若有多个相同的数,因只删除一个)
3.查询x数的排名(若有多个相同的数,因输出最小的排名)
4.查询排名为x的数
5.求x的前驱(前驱定义为小于x,且最大的数)
6.求x的后继(后继定义为大于x,且最小的数)
说明:
son[k][0] 表示 k 的左儿子, son[k][1] 表示 k 的右儿子
size[k] 表示以 k 为根的子树的节点数
w[k] 表示节点 k 的权值
rnd[k] 表示节点 k 的优先级
g[k] 表示节点 k 的个数(有可能有重复的)
0.旋转
最关键的操作,运用了二进制优化,使左旋和右旋代码合并。

给张左旋右旋的图,可以通过这张图来推出左旋右旋代码。(来自百度百科)
1 //通过旋转来维护小根堆 2 //因为通过旋转根是会变的,所以得加 & 3 // x 为 0 是 右旋,x 为 1 是 左旋 4 //但是不必知道是左旋还是右旋 5 //只知道旋转一个节点的孩子就相当于把这个孩子旋转到父亲上 6 void turn(int &k, int x) 7 { 8 //位运算优化 ^ 可表示兄弟节点, 使得左旋和右旋合并 9 int t = son[k][x]; 10 son[k][x] = son[t][x ^ 1]; 11 son[t][x ^ 1] = k; 12 size[t] = size[k]; 13 size[k] = size[son[k][0]] + size[son[k][1]] + g[k]; 14 k = t; 15 }
1.插入x
可以看出,每次插入都是插入叶节点,所以直接递归处理就好。
同时注意更新size数组,通过旋转保持堆的性质。
1 void insert(int &k, int x)//小根堆_插入 2 { 3 if(!k) 4 { 5 k = ++tot; 6 w[k] = x; 7 rnd[k] = rand(); 8 g[k] = size[k] = 1; 9 return; 10 } 11 size[k]++; 12 if(w[k] == x) g[k]++; 13 else if(w[k] < x) 14 { 15 insert(son[k][1], x); 16 if(rnd[son[k][1]] < rnd[k]) turn(k, 1); 17 } 18 else 19 { 20 insert(son[k][0], x); 21 if(rnd[son[k][0] < rnd[k]]) turn(k, 0); 22 } 23 }
2.删除x
1.如果是叶子节点就直接删除
2.如果只有一个儿子,就子继父位
3.如果有两个儿子,就通过旋转,把较小儿子翻转到上面,把当前节点翻转到下面,知道当前节点为情况1或2
1 //通过旋转使得被删除点转移得到叶子节点上或只有一个儿子 2 void del(int &k, int x)//删除 3 { 4 if(!k) return; 5 if(w[k] == x) 6 { 7 if(g[k] > 1) 8 { 9 g[k]--; 10 size[k]--; 11 return; 12 } 13 //只有一个儿子, 直接继承 14 if(son[k][0] * son[k][1] == 0) k = son[k][0] + son[k][1]; 15 //旋转后须满足小根堆性质,所以右旋,旋转后 k 值改变,继续删除 16 else if(rnd[son[k][0]] < rnd[son[k][1]]) turn(k, 0), del(k, x); 17 //反之左旋 18 else turn(k, 1), del(k, x); 19 } 20 else 21 { 22 size[k]--; 23 if(w[k] < x) del(son[k][1], x); 24 else del(son[k][0], x); 25 } 26 }
3.查询x的排名
递归处理,具体细节看代码。
1 //查询x数的排名(若有多个相同的数,因输出最小的排名) 2 int get_rank(int k, int x) 3 { 4 if(!k) return 0; 5 if(w[k] == x) return size[son[k][0]] + 1; 6 else if(w[k] < x) return size[son[k][0]] + g[k] + get_rank(son[k][1], x); 7 else return get_rank(son[k][0], x); 8 }
4.查询排名为x的数
递归处理,具体细节看代码。
1 //找第 x 大的数 2 int get_kth(int k, int x) 3 { 4 if(!k) return 0; 5 if(size[son[k][0]] >= x) return get_kth(son[k][0], x); 6 else if(size[son[k][0]] + g[k] < x) return get_kth(son[k][1], x - size[son[k][0]] - g[k]); 7 else return w[k]; 8 }
5.求x的前驱
递归处理,具体细节看代码。
1 // x 的前驱是比 x 小的数中最大的那个 2 void get_pre(int k, int x) 3 { 4 if(!k) return; 5 if(w[k] >= x) get_pre(son[k][0], x); 6 else ans = k, get_pre(son[k][1], x); 7 }
6.求x的后继
递归处理,具体细节看代码。
1 // x 的后继是比 x 大的数中最小的那个 2 void get_suc(int k, int x) 3 { 4 if(!k) return; 5 if(w[k] <= x) get_suc(son[k][1], x); 6 else ans = k, get_suc(son[k][0], x); 7 }
最后是凑字数的主程序
1 int main() 2 { 3 int i, opt, x; 4 scanf("%d", &n); 5 for(i = 1; i <= n; i++) 6 { 7 scanf("%d %d", &opt, &x); 8 switch(opt) 9 { 10 case 1: insert(root, x); break; 11 case 2: del(root, x); break; 12 case 3: printf("%d\n", get_rank(root, x)); break; 13 case 4: printf("%d\n", get_kth(root, x)); break; 14 case 5: ans = 0; get_pre(root, x); printf("%d\n", w[ans]); break; 15 case 6: ans = 0; get_suc(root, x); printf("%d\n", w[ans]); break; 16 } 17 } 18 return 0; 19 }
完整代码
1 #include <cstdio> 2 #include <cstdlib> 3 4 using namespace std; 5 6 int n, root, ans, tot; 7 int son[100001][2], size[100001], w[100001], rnd[100001], g[100001]; 8 //son[k][0] 表示 k 的左儿子, son[k][1] 表示 k 的右儿子 9 //size[k] 表示以 k 为根的子树的节点数 10 //w[k] 表示节点 k 的权值 11 //rnd[k] 表示节点 k 的优先级 12 //g[k] 表示节点 k 的个数(有可能有重复的) 13 14 //通过旋转来维护小根堆 15 //因为通过旋转根是会变的,所以得加 & 16 // x 为 0 是 右旋,x 为 1 是 左旋 17 //但是不必知道是左旋还是右旋 18 //只知道旋转一个节点的孩子就相当于把这个孩子旋转到父亲上 19 void turn(int &k, int x) 20 { 21 //位运算优化 ^ 可表示兄弟节点, 使得左旋和右旋合并 22 int t = son[k][x]; 23 son[k][x] = son[t][x ^ 1]; 24 son[t][x ^ 1] = k; 25 size[t] = size[k]; 26 size[k] = size[son[k][0]] + size[son[k][1]] + g[k]; 27 k = t; 28 } 29 30 void insert(int &k, int x)//小根堆_插入 31 { 32 if(!k) 33 { 34 k = ++tot; 35 w[k] = x; 36 rnd[k] = rand(); 37 g[k] = size[k] = 1; 38 return; 39 } 40 size[k]++; 41 if(w[k] == x) g[k]++; 42 else if(w[k] < x) 43 { 44 insert(son[k][1], x); 45 if(rnd[son[k][1]] < rnd[k]) turn(k, 1); 46 } 47 else 48 { 49 insert(son[k][0], x); 50 if(rnd[son[k][0] < rnd[k]]) turn(k, 0); 51 } 52 } 53 54 //通过旋转使得被删除点转移得到叶子节点上或只有一个儿子 55 void del(int &k, int x)//删除 56 { 57 if(!k) return; 58 if(w[k] == x) 59 { 60 if(g[k] > 1) 61 { 62 g[k]--; 63 size[k]--; 64 return; 65 } 66 //只有一个儿子, 直接继承 67 if(son[k][0] * son[k][1] == 0) k = son[k][0] + son[k][1]; 68 //旋转后须满足小根堆性质,所以右旋,旋转后 k 值改变,继续删除 69 else if(rnd[son[k][0]] < rnd[son[k][1]]) turn(k, 0), del(k, x); 70 //反之左旋 71 else turn(k, 1), del(k, x); 72 } 73 else 74 { 75 size[k]--; 76 if(w[k] < x) del(son[k][1], x); 77 else del(son[k][0], x); 78 } 79 } 80 81 //查询x数的排名(若有多个相同的数,因输出最小的排名) 82 int get_rank(int k, int x) 83 { 84 if(!k) return 0; 85 if(w[k] == x) return size[son[k][0]] + 1; 86 else if(w[k] < x) return size[son[k][0]] + g[k] + get_rank(son[k][1], x); 87 else return get_rank(son[k][0], x); 88 } 89 90 //找第 x 大的数 91 int get_kth(int k, int x) 92 { 93 if(!k) return 0; 94 if(size[son[k][0]] >= x) return get_kth(son[k][0], x); 95 else if(size[son[k][0]] + g[k] < x) return get_kth(son[k][1], x - size[son[k][0]] - g[k]); 96 else return w[k]; 97 } 98 99 // x 的前驱是比 x 小的数中最大的那个 100 void get_pre(int k, int x) 101 { 102 if(!k) return; 103 if(w[k] >= x) get_pre(son[k][0], x); 104 else ans = k, get_pre(son[k][1], x); 105 } 106 107 // x 的后继是比 x 大的数中最小的那个 108 void get_suc(int k, int x) 109 { 110 if(!k) return; 111 if(w[k] <= x) get_suc(son[k][1], x); 112 else ans = k, get_suc(son[k][0], x); 113 } 114 115 int main() 116 { 117 int i, opt, x; 118 scanf("%d", &n); 119 for(i = 1; i <= n; i++) 120 { 121 scanf("%d %d", &opt, &x); 122 switch(opt) 123 { 124 case 1: insert(root, x); break; 125 case 2: del(root, x); break; 126 case 3: printf("%d\n", get_rank(root, x)); break; 127 case 4: printf("%d\n", get_kth(root, x)); break; 128 case 5: ans = 0; get_pre(root, x); printf("%d\n", w[ans]); break; 129 case 6: ans = 0; get_suc(root, x); printf("%d\n", w[ans]); break; 130 } 131 } 132 return 0; 133 }

浙公网安备 33010602011771号