@Aonrbet
2020-07-08T09:48:11.000000Z
字数 3425
阅读 566
Study
前置芝士
BST的性质:
根节点左子树的值均小于等于根节点,右子树的值均大于根节点
我们需要支持以下操作
inline void split(int now, int &x, int &y, int k){ //裂开if(!now) return (void)(x = y = 0); //如果没有根即没有树可裂,x,y均为0if(val[now] <= k){x = now; //如果根的值小于等于k则k要大于now左子树所有的值,所以把左子树裂开,让右子树接着裂split(ch[now][1], ch[x][1], y, k);}else{y = now; //相反split(ch[now][0], x, ch[y][0], k);}up(now);//裂开以后所有比k小的值均在x树上,比k大的值均在y树上}
inline void merge(int &now, int x, int y){if(!siz[x] or !siz[y]) return (void)(now = siz[x] ? x : y); //x 和 y 有一个空树的话直接返回不空树if(key[x] < key[y]){ //当x的键值小于y的键值 ,让y和x的右子树并now = x;merge(ch[now][1], ch[x][1], y);up(now);}else{now = y;//相反merge(ch[now][0], x, ch[y][0]);up(now);}}
如何删除一个数num,很巧妙的方法
1.我们把root树以num为关键字裂开,此时x树上的值均小于等于num
2.我们再去裂x树,此时以num-1为关键字,则裂开的两个树中有一个树上的值均等于 num(我们设这颗树为z)
3.直接合并z的左右子树,此时已经把z的根节点移除
4.合并
inline void del(int num){split(root, x, y, num);split(x, x, z, num-1);merge(z, ch[z][0], ch[z][1]);merge(x, x, z);merge(root, x, y);}
很简单
inline void ins(int num){split(root, x, y, num);merge(x, x, new_node(num));merge(root, x, y);}
寻找第k排名的数
(暂时还不透彻,会补上注释的....汗)
inline int kth(int now, int k){while(1){if(k <= siz[ch[now][0]]){now = ch[now][0];}else{if(k == siz[ch[now][0]] + 1) return now;else{k -= siz[ch[now][0]] + 1;now = ch[now][1];}}}}
num的排名就是所有小于num的数的数量+1,此时已经显然
inline int rnk(int num){split(root, x, y, num-1);int res = siz[x] + 1;merge(root, x, y);return res;}
前驱怎么找?
前驱是不是所有小于num的值里最大的值!
我们以num-1为关键字去把root树裂开,此时x树上的值是所有小于num的数,
我们再找x树里面排名为size[x]的数,是不是就是所有小于num的数里面最大的数?即前驱。
inline int pre(int num){ //找前驱split(root, x, y, num-1);int res = val[kth(x,siz[x])];merge(root, x, y);return res;}
后继怎么找...
后继是不是所有大于num的值里最小的值.....
我们以num为关键字去把root树裂开,此时y树上的值是所有大于num的数,
我们再找y树里面排名为1的数,是不是就是所有大于num的数里面最小的数?即后继。
inline int nxt(int num){split(root, x, y, num);int res = val[kth(y,1)];merge(root, x, y);return res;}
总代码看不看无所谓的吧,反正就是并在一起了....
code
#include <bits/stdc++.h>using namespace std;const int maxn = 1e5 + 10;struct Treap{int ch[maxn][2], val[maxn], key[maxn], siz[maxn];int sum, root, x, y, z, n; //sum记录节点编号inline void up(int now){ //维护一下大小siz[now] = siz[ch[now][0]] + siz[ch[now][1]] + 1; //它的大小就等于左右儿子大小的和+自己这个点}inline int new_node(int num){ //新建一个点siz[++sum] = 1; //新建一个树(点),但大小为1val[sum] = num; //值为numkey[sum] = rand(); //随机return sum; //返回节点编号}inline void split(int now, int &x, int &y, int k){if(!now) return (void)(x = y = 0);if(val[now] <= k){x = now;split(ch[now][1], ch[x][1], y, k);}else{y = now;split(ch[now][0], x, ch[y][0], k);}up(now);}inline void merge(int &now, int x, int y){if(!siz[x] or !siz[y]) return (void)(now = siz[x] ? x : y);if(key[x] < key[y]){now = x;merge(ch[now][1], ch[x][1], y);up(now);}else{now = y;merge(ch[now][0], x, ch[y][0]);up(now);}}inline void del(int num){split(root, x, y, num);split(x, x, z, num-1);merge(z, ch[z][0], ch[z][1]);merge(x, x, z);merge(root, x, y);}inline void ins(int num){split(root, x, y, num);merge(x, x, new_node(num));merge(root, x, y);}inline int kth(int now, int k){while(1){if(k <= siz[ch[now][0]]){now = ch[now][0];}else{if(k == siz[ch[now][0]] + 1) return now;else{k -= siz[ch[now][0]] + 1;now = ch[now][1];}}}}inline int rnk(int num){split(root, x, y, num-1);int res = siz[x] + 1;merge(root, x, y);return res;}inline int pre(int num){split(root, x, y, num-1);int res = val[kth(x,siz[x])];merge(root, x, y);return res;}inline int nxt(int num){split(root, x, y, num);int res = val[kth(y,1)];merge(root, x, y);return res;}void main(){root = 0, x = y = z = 0;scanf("%d", &n);for(int i = 1, q, w; i <= n; i ++){scanf("%d%d", &q, &w);if(q == 1) ins(w);if(q == 2) del(w);if(q == 3) printf("%d\n", rnk(w));if(q == 4) printf("%d\n", val[kth(root,w)]);if(q == 5) printf("%d\n", pre(w));if(q == 6) printf("%d\n", nxt(w));}}}FHQ;signed main(){FHQ.main();return 0;}
wanqua