建议大家看看电子科大acm对splay树的讲解,B站上有
附上模板
//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
using namespace std;
#define MAXN 1000006
struct node{
int data;
}a[MAXN];//a存具体插入的数
int fa[MAXN],s[MAXN][2],size[MAXN],num[MAXN];
//fa存父节点,s存左右孩子节点编号,size[i]存i为根的子树中的数的个数;
//num[i]中节点i中数值相同的数的个数。
int sz,root;
//sz表示树上的节点数,root存当前树根节点
void clear(int x){
s[x][0]=s[x][1]=fa[x]=size[x]=num[x]= a[x].data = 0;
}
//看x是父节点的左还是右孩子
int whichson(int x){return s[fa[x]][1] == x;}//1代表右孩子
//维护x节点上的数据信息
void maintain(int x){
size[x] = size[s[x][0]] + size[s[x][1]] + num[x];
}
//将son设置为fa的w方向的孩子
void setson(int son,int f,int w){
if(son != 0) fa[son] = f;
if(f != 0) s[f][w] = son;
}
void rotate(int x){ //此旋转有玄机,详细解说可见bilibili中电子科技大学算法课堂。
//f是x的父亲,ff是x的爷爷,w是x是父亲f的左或右孩子的方向。
int f = fa[x], ff = fa[f] , w = whichson(x);
//wf是f是ff的左或右孩子的方向
int wf = whichson(f);
int p = s[x][!w];//记录x的与x不同方向孩子节点编号。
setson(p,f,w);
setson(x,ff,wf);
setson(f,x,!w);
maintain(f);
maintain(x);
}
void splay(int x){
for(;fa[x];rotate(x)){
//当父亲与x同是其上一层的左孩子或右孩子,即x,fa[x],fa[fa[x]]三点一线时,便先旋父亲。
if(fa[fa[x]] && whichson(x) == whichson(fa[x]))
rotate(fa[x]);
}
root = x;
}
/*插入操作:
1.如果root=0,即树为空的话,做一些特殊的处理后返回即可。
2.按照二叉查找树的方法一直向下找,如果遇到一个结点的关键字等于当前要插入的点的话,
我们就等于把这个结点加了一个权值。因为在二叉搜索树中是不可能出现两个相同的点的。
并且要将当前点和它父亲结点的各项值更新一下,并做splay;如果已经到了最底下了,那么就
可以直接插入。整个树的大小要+1,新结点的左儿子右儿子、父亲还有各项值要一一对应。并且
最后要做一下他父亲的maintain,并做splay。
*/
void insert(int x){
if(root==0){
sz++;s[sz][0]=s[sz][1]=fa[sz] = 0;
root = sz;
size[sz] = num[sz] = 1;
a[sz].data = x;
return;
}
int now = root,f = 0;
while(1){
if(x == a[now].data){
num[now] ++;maintain(now);maintain(f);splay(now);break;
}
f = now;
now = s[now][a[now].data < x];
if(now == 0){//到达插入的位置
sz++;
s[sz][0] = s[sz][1] = 0;
fa[sz] = f;
size[sz] = num[sz] = 1;
s[f][a[f].data < x] = sz;
a[sz].data = x;
maintain(f);
splay(sz);
break;
}
}
}
//查找x的排名
/*
初始化:ans=0,当前点=root
如果x比当前结点小,即应该向左子树寻找,ans不用改变。
如果x比当前结点大,即应该向右子树寻找,ans需要加上左子树的大小以及根的权值大小。
最后做splay
*/
int find(int x){
int now = root,ans = 0;
while(1){
if(x < a[now].data){
now = s[now][0];
}
else{
ans += s[now][0] ? size[s[now][0]]:0;
if(x == a[now].data){
splay(now);
return ans + 1;
}
ans += num[now];
now = s[now][1];
}
}
}
/*
初始化:当前点root
如果当前点有左子树,并且x比左子树的大小小的话,即向左子树寻找;
如果 左子树的数量< x <=左子树数量+该节点上的权值,则该节点便是要找的值。
否则 x-前面的数量,向右子树寻找。
*/
int findx(int x){
int now = root;
while(1){
if(s[now][0] && x <= size[s[now][0]]) now = s[now][0];
else{
int tmp = (s[now][0] ? size[s[now][0]] :0) + num[now];
if(x <= tmp) return a[now].data;
x-= tmp;
now = s[now][1];
}
}
}
//找根节点的左子树中的最右节点 即根节点的前驱
int pre(){
int now = s[root][0];
while(s[now][1]) now = s[now][1];
return now;
}
//找根节点的右子树中的最左节点 即根节点的后继
int nxt(){
int now = s[root][1];
while(s[now][0]) now = s[now][0];
return now;
}
void del(int x){
int whatever = find(x);
//当根节点中有多余1个以上的数,则删除一个即可,平衡树上节点数不变。
if(num[root] > 1) {
num[root]--;maintain(root);return;
}
//当根节点无左右子树时,直接删掉根节点即可
if(!s[root][0] && !s[root][1]){
clear(root);
root = 0; return;
}
//如果无左子树则将右子树作为根节点。
if(!s[root][0]){
int oldroot = root;
root = s[root][1];
fa[root] = 0;
clear(oldroot);
return;
}
//如果无左右子树则将左子树作为根节点。
if(!s[root][1]){
int oldroot = root;
root = s[root][0];
fa[root] = 0;
clear(oldroot);
return;
}
//如果左右子树均有,则找到根节点的前驱,并将其splay至根节点,然后
//将oldroot的右子树直接接至旋至根节点的新的节点的右孩子。
//维护根节点上信息
int leftbig = pre(),oldroot = root;
splay(leftbig);
s[root][1] = s[oldroot][1];
fa[s[oldroot][1]] = root;
clear(oldroot);
maintain(root);
}
int main(){
int n,opt,x;
cin >> n;
for(int i= 1;i<= n;i++){
scanf("%d%d",&opt,&x);
if(opt == 1) insert(x);
else if(opt == 2) del(x);
else if(opt == 3) printf("%d\n",find(x));
else if(opt == 4) printf("%d\n",findx(x));
else if(opt == 5) {
insert(x);
printf("%d\n",a[pre()].data);
del(x);
}
else if(opt == 6){
insert(x);
printf("%d\n",a[nxt()].data);
del(x);
}
}
return 0;
}