————————————————
版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ModestCoder_/article/details/90139481
清空一个节点
确定一个节点是父亲的左儿子还是右儿子
更新一个节点
把一个点连到另一点下面
上旋
splay
插入一个点
查询一个数的排名
查询排名为k的数
前驱、后继
删除一个点
#include <bits/stdc++.h> #define maxn 100010 using namespace std; int sz, rt, f[maxn], key[maxn], size[maxn], recy[maxn], son[maxn][2]; inline int read(){ int s = 0, w = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') w = -1; for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48); return s * w; } void clear(int x){ f[x] = key[x] = size[x] = recy[x] = son[x][0] = son[x][1] = 0; //5个数组全部清零 } int get(int x){ return son[f[x]][1] == x; //如果自己是父亲的右儿子,返回1;否则返回0(0为左儿子,1为右儿子) } void update(int x){ if (x){//如果这个点存在 size[x] = recy[x];//自己重复的次数先累计 if (son[x][0]) size[x] += size[son[x][0]]; if (son[x][1]) size[x] += size[son[x][1]]; //如果存在儿子,把儿子的size累积到自己 //然后发现一个问题,更新自己的size时,必须保证儿子的size是正确的 //所以后面的操作,当牵扯到儿子和父亲时,应该先更新新儿子,后更新新父亲 } } void connect(int x, int y, int z){//x连到y下面,关系为z if (x) f[x] = y;//存在x,则x的父亲为y if (y) son[y][z] = x;//存在y,y的z关系儿子为x } void rotate(int x){//上旋x int fa = f[x], ffa = f[fa], m = get(x), n = get(fa);//确定x,fa的关系 connect(son[x][m ^ 1], fa, m);//把要转的儿子转到父亲下,关系为m connect(fa, x, m ^ 1);//把父亲转到自己下面,关系为m^1 connect(x, ffa, n);//把自己转到父亲的父亲下,关系为n update(fa), update(x);//先更新fa,再更新自己,可以自己想想为什么是这个顺序 } void splay(int x){ for (int fa; fa = f[x]; rotate(x))//每次总是旋转自己 if (f[fa]) rotate(get(x) == get(fa) ? fa : x);//如果有爷爷(父亲的父亲),看父亲与父亲的父亲的关系决定转哪个 rt = x;//别忘了,把根赋为当前点 } void insert(int x){ if (!rt){//树中没有一个节点 rt = ++sz; key[rt] = x; size[rt] = recy[rt] = 1; son[rt][0] = son[rt][1] = 0;//赋初值 return; } int now = rt, fa = 0; while (1){ if (key[now] == x){//树中已有此点,重复+1 ++recy[now]; update(now); update(fa); splay(now);//splay一下,保证平衡 return; } fa = now, now = son[now][x > key[now]];//满足二叉查找树的性质,往下跑 if (!now){ ++sz; key[sz] = x; size[sz] = recy[sz] = 1;//赋初值 f[sz] = fa;//父亲是fa son[fa][x > key[fa]] = sz;//更新父亲的新儿子 update(fa);//更新父亲的size splay(sz);//splay一下,保证平衡 return; } } } int find(int x){//查排名 int now = rt, ans = 0; while (1){ if (x < key[now]){ now = son[now][0]; continue;//在左子树中 } ans += size[son[now][0]];//排名加上左子树节点个数 if (x == key[now]){ splay(now); return ans + 1; }//值等于当前点,splay一下,保证平衡,排名+1为当前排名 ans += recy[now];//排名加上当前节点的数的个数 now = son[now][1];//在右子树中 } } int kth(int x){//查找排名为x的数 int now = rt; while (1){ if (son[now][0] && x <= size[son[now][0]]){//在左子树中 now = son[now][0]; continue; } if (son[now][0]) x -= size[son[now][0]];//存在左儿子,排名减去左子树节点数 if (x <= recy[now]){ splay(now); return key[now]; }//说明就是当前点,splay一下,保证平衡,退出 x -= recy[now];//排名减去当前节点数的个数 now = son[now][1];//在右子树中 } } int pre(){//前驱为左子树中最大的那个 int now = son[rt][0]; while (son[now][1]) now = son[now][1]; return now; } int nxt(){//后继为右子树中最小的那个 int now = son[rt][1]; while (son[now][0]) now = son[now][0]; return now; } void del(int x){ int no_use = find(x);//find主要是把当前数的对应点找到,然后旋到根,返回值的排名在这里没用 if (recy[rt] > 1){//情况1:有重复,重复-1,更新,退出 --recy[rt]; update(rt); return; } //接下来都是没有重复的情况 if (!son[rt][0] && !son[rt][1]){//情况2:没有儿子,直接清空 clear(rt); rt = 0; return; } if (!son[rt][0]){//情况3:没有左儿子,只有右儿子,右儿子变成根,清除自己 int tmp = rt; f[rt = son[rt][1]] = 0; clear(tmp); return; } if (!son[rt][1]){//情况4:没有右儿子,只有左儿子,左儿子变成根,清除自己 int tmp = rt; f[rt = son[rt][0]] = 0; clear(tmp); return; } //情况5:两个儿子都有,这是需要一个很简便的做法 //把前驱splay到根,保持左子树其他节点不用动 //原根右儿子变成前驱的右儿子 //原根功成身退,清除掉 //最后对前驱的size进行更新 int tmp = rt, left = pre(); splay(left); connect(son[tmp][1], rt, 1); clear(tmp); update(rt); } int main(){ int M = read(); while (M--){ int opt = read(), x = read(); if (opt == 1) insert(x); if (opt == 2) del(x); if (opt == 3) printf("%d ", find(x)); if (opt == 4) printf("%d ", kth(x)); if (opt == 5){ insert(x); printf("%d ", key[pre()]); del(x); } if (opt == 6){ insert(x); printf("%d ", key[nxt()]); del(x); } } return 0; }