OI-wiki写的非常好,所以在这里加以自己的注释理解存一下模板qwq。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const int N = 1e5 + 5;
int root,tot,fa[N],ch[N][2],val[N],cnt[N],siz[N];
struct Splay{
void maintain(int x){
siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + cnt[x];
}//pushup
bool get(int x){return x == ch[fa[x]][1];}//是爹的左儿子(0)还是右儿子(1)
void clear(int x){//销毁结点
ch[x][0] = ch[x][1] = fa[x] = val[x] = siz[x] = cnt[x] = 0;
}
void rotate(int x){//旋转操作
int y = fa[x],z = fa[y],chk = get(x);//爹,爹的爹,是爹的哪个儿子
ch[y][chk] = ch[x][chk^1];//如果我是爹的左儿子,把我的右儿子给爹的左儿子//如果是右儿子,把我的左儿子给爹的右儿子
if(ch[x][chk^1])fa[ch[x][chk^1]] = y;//把这个儿子的爹改成我的爹
ch[x][chk^1] = y;//父子关系换了,哈哈!
fa[y] = x;fa[x] = z;//哈哈!哈哈!
if(z)ch[z][y == ch[z][1]] = x;//如果爹的爹存在,更新儿子,你滴儿子是我辣!
maintain(x);maintain(y);//pushup pushup
}
void splay(int x){
for(int f;f = fa[x];rotate(x))//我还有爹吗,有就旋
if(fa[f])rotate(get(x) == get(f) ? f : x);//如果有爹,相同的话要先旋爹
root = x;//我是根辣!
}
void ins(int k){
if(!root){
val[++tot] = k;
cnt[tot]++;
root = tot;
maintain(root);return;//空就新建结点
}
int cur = root,f = 0;
while(1){
if(val[cur] == k){//这个数存在那么就cnt+1就好了
cnt[cur]++;
maintain(cur);maintain(f);
splay(cur);//别忘了
break;
}
f = cur;
cur = ch[cur][val[cur] < k];//当前节点比k小就往右,否则往左
if(!cur){//走不下去了,就新建节点,完事!
val[++tot] = k;
cnt[tot]++;
fa[tot] = f;
ch[f][val[f] < k] = tot;//别忘了爹的儿子
maintain(tot);maintain(f);splay(tot);break;
}
}
}
int rk(int k){
int res = 0,cur = root;
while(1){
if(k < val[cur])cur = ch[cur][0];//当前节点大了就往左查
else{
res += siz[ch[cur][0]];//否则加上左子树的大小,注意不加自己
if(k == val[cur]){//如果刚好查到了那么就是返回答案了,比他小的个数 + 1
splay(cur);
return res + 1;
}
res += cnt[cur];//比当前结点大,那么加上当前结点继续右子树
cur = ch[cur][1];//右子树
}
}
}
int kth(int k){
int cur = root;
while(1){
if(ch[cur][0] && k <= siz[ch[cur][0]])cur = ch[cur][0];//左儿子够
else{
k -= siz[ch[cur][0]] + cnt[cur];//权值线段树啦~~
if(k <= 0){//够了就是当前结点了
splay(cur);return val[cur];
}
cur = ch[cur][1];//不够往右跳
}
}
}
int pre(){
int cur = ch[root][0];//前驱,就是先插入这个数,把他旋到根,那么就是他左儿子的右儿子最深那个,也就是最大的小于他的
if(!cur)return cur;//不存在
while(ch[cur][1])cur = ch[cur][1];//跳最深的右儿子
splay(cur);//嗯,别忘了!
return cur;
}
int nxt(){
int cur = ch[root][1];//后继同理
if(!cur)return cur;
while(ch[cur][0])cur = ch[cur][0];//不过跳右子树的左儿子
splay(cur);//咳咳,别忘了!
return cur;
}
void del(int k){
rk(k);//就是把他的结点扔到根
if(cnt[root] > 1){//有一堆,删一个就好了
cnt[root]--;
maintain(root);
return;
}
if(!ch[root][0] && !ch[root][1]){//这棵树只有他一个
clear(root);//那么就删掉了
root = 0;
return ;
}
if(!ch[root][0]){//只有右儿子
int cur = root;
root = ch[root][1];//删掉就好了,更新一下右儿子的父亲
fa[root] = 0;
clear(cur);
return;
}
if(!ch[root][1]){
int cur = root;
root = ch[root][0];//左边同理
fa[root] = 0;
clear(cur);
return;
}
int cur = root;//最麻烦的!
int x = pre();//先找到前驱
fa[ch[cur][1]] = x;//右儿子挂到前驱下
ch[x][1] = ch[cur][1];//前驱的右儿子是他的右儿子
clear(cur);//删了就好了
maintain(root);
}
}tree;
signed main(){
int n,opt,x;
read(n);
while(n--){
read(opt,x);
if(opt == 1)tree.ins(x);
else if(opt == 2)tree.del(x);
else if(opt == 3)printf("%d\n",tree.rk(x));
else if(opt == 4)printf("%d\n",tree.kth(x));
else if(opt == 5)tree.ins(x),printf("%d\n",val[tree.pre()]),tree.del(x);
else tree.ins(x),printf("%d\n",val[tree.nxt()]),tree.del(x);
}
}
标签:ch,cur,val,int,Splay,fa,平衡,root,模板
From: https://www.cnblogs.com/wsxxs/p/16659271.html