HYSBZ - 3224 Tyvj 1728 普通平衡树 (splay树板子题)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3224
解题思路:
动态的插入数
询问x为插入的数中第几个
询问第x数是啥
询问<x的最大的一个
询问>x的最小的一个
splay树板子题。
权当模板存下。
代码较长,抄板子的时候一定得仔细,抄错一点地方都不对
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000007;
int f[maxn];//i的父亲节点
int ch[maxn][2];//ch[i][0]表示i的左儿子,ch[i][j]表示i的右儿子
int key[maxn];//节点i代表的数字
int cnt[maxn];//cnt[i]表示i节点的关键字出现的次数,相当于权值
int size[maxn];//size[i]表示包括i的这个子树的大小。
int sz = 0;//整棵树的大小
int rt = 0;//整棵树的根
//将当前各项值都清0 (用于删除之后)
void clear(int x){
f[x] = cnt[x] = ch[x][0] = ch[x][1] =size[x] = key[x] =0;
}
//判断这个节点是左孩子还是右孩子 1是右孩子 0是左孩子
bool get(int x){
return ch[f[x]][1] == x;
}
//更新当前节点的size值(用于修改之后)
void pushup(int x){
//更新这个节点的权值
if(x){
size[x] = cnt[x];
if(ch[x][0]) size[x] += size[ch[x][0]];
if(ch[x][1]) size[x] += size[ch[x][1]];
}
}
void rotate(int x)
{
int old=f[x],oldf=f[old],which=get(x);
ch[old][which]=ch[x][which^1]; f[ch[old][which]]=old; //这两句的意思是:
//我的儿子过继给我的爸爸;同时处理父子两个方向上的信息
ch[x][which^1]=old; f[old]=x;
//我给我爸爸当爹,我爸爸管我叫爸爸
f[x]=oldf;//我的爷爷成了我的爸爸
if (oldf) ch[oldf][ch[oldf][1]==old]=x;
pushup(old); pushup(x);//分别维护信息
}
//splay树平衡 把x旋转为根节点
void splay(int x){
for(int fa;fa=f[x];rotate(x)){
if(f[fa]){
rotate((get(x)==get(fa))?fa:x);
}
}
rt = x;
}
//x为插入点的权值
void insert(int x){
if(rt == 0){///空树
sz++;
key[sz] = x;
rt = sz;
cnt[sz] = size[sz] =1;
f[sz] = ch[sz][0] =ch[sz][1] = 0;
return;
}
int now = rt,fa = 0;
while(1){
if(x == key[now]){//这个值已经出现过了
cnt[now]++;
pushup(now);
pushup(fa);
splay(now);
return;
}
fa = now;
now = ch[now][key[now]<x];//往左还是往右走
if(now == 0){
sz++;
//sz 为当前这个节点点的编号
size[sz] = cnt[sz] = 1;
ch[sz][0] = ch[sz][1] = 0;
ch[fa][x>key[fa]] = sz;///根据加入的点的顺序标号
f[sz] = fa;
key[sz] = x;
pushup(fa);
splay(sz);
return ;
}
}
}
//查询x的排名
int rnk(int x){
int now = rt;
int ans = 0;
while(1){
if(x<key[now]) now = ch[now][0];//左子树
else{
ans += size[ch[now][0]];//加上左子树的贡献
if(x == key[now]){
splay(now);
return ans + 1;
}
ans += cnt[now];//次数
now = ch[now][1];//到右孩子
}
}
}
//查询排名为x的点
int kth(int x){
int now = rt;
while(1){
//左孩子
if(ch[now][0]&&x<=size[ch[now][0]]){
now = ch[now][0];
}else{
int tmp = size[ch[now][0]]+cnt[now];//左半部分
if(x<=tmp) return key[now];
x-=tmp;
now = ch[now][1];
}
}
}
//前驱 找小于根节点的最大的那个 也就是找左子树中最大的数
int pre(){
int now=ch[rt][0];
while(ch[now][1]){
now = ch[now][1];
}
return now;
}
//后继 找大于根节点的最小的那个 也就是找右子树中最小的那个
int next(){
int now = ch[rt][1];
while(ch[now][0]){
now = ch[now][0];
}
return now;
}
//del 删除x这个数
void del(int x){
rnk(x);//把x旋转到根节点的地方
if(cnt[rt]>1){//有好几个x
cnt[rt] --;
pushup(rt);
return ;
}
if(!ch[rt][0]&&!ch[rt][1]){//没孩子
clear(rt);
rt = 0;
return;
}
if(!ch[rt][0]){
int oldrt = rt;
rt = ch[rt][1];
f[rt] = 0;
clear(oldrt);
return;
}else if(!ch[rt][1]){
int oldrt = rt;
rt = ch[rt][0];
f[rt] = 0;
clear(oldrt);
return;
}
int oldrt = rt;
int leftbig = pre();
splay(leftbig);
ch[rt][1]=ch[oldrt][1];
f[ch[oldrt][1]] = rt;//此时的rt就是leftbig
clear(oldrt);
pushup(rt);
}
int main(){
int t;
cin>>t;
int op,x;
while(t--){
scanf("%d%d",&op,&x);
if(op==1){
insert(x);
}else if(op==2){
del(x);
}else if(op==3){
cout<<rnk(x)<<endl;
}else if(op==4){
cout<<kth(x)<<endl;
}else if(op==5){
insert(x); printf("%d\n",key[pre()]); del(x);
}else if(op==6){
insert(x); printf("%d\n",key[next()]); del(x);
}
}
return 0;
}