noco
2018-04-29 15:56:22
题目为bzoj3224
传送门
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+1e3+7;
int num[N];//标号为x的相等数字 有num个
int sz[N];//此节点子树大小
int rev[N];
int fa[N],son[N][2];//当前点父亲 左儿子0||右儿子1
int rt;//根节点标号
int key[N];//当前点数值
int tot;//平衡树中节点数目
void update(int x)
{
sz[x]=sz[son[x][0]]+sz[son[x][1]]+num[x];
}//更新子树大小 (包括自己)
void rotate(int x)
{
int y=fa[x],z=fa[y],t=(son[y][0]==x);//相当于是y&x在旋 但这个过程y x要换父亲 因此要引入z t用来自适应
if(z)//非根节点
son[z][son[z][1]==y]=x;//将z的某一个儿子设为x
fa[x]=z;//连上
son[y][!t]=son[x][t];//相连最近儿子(左儿或右儿)
fa[son[x][t]]=y;
son[x][t]=y;//将y连到x下
fa[y]=x;
update(y);//更新子树大小
update(x);
}
void splay(int x,int s)//当前节点 旋到何处结束
{
while(fa[x]!=s)//即旋到x连到s下结束
{
int y=fa[x],z=fa[y];//z为x的爷爷 y的父亲
if(z!=s)//控制边界
{//保证平衡树平衡
if((son[y][0]==x)^(son[z][0]==y))//x,y,z不在一条链上
rotate(x);//正常操作
else//否则折叠这个长链
rotate(y);
}
rotate(x);//双旋 上边的操作并未将当前点继续上传 因此要再转一下
}
if(!s)//换根 即旋转到根结束
rt=x;
}
void insert(int &x,int s,int v)//插入:标号 祖先 数值
{
if(!x)//为虚根节点 并且根节点只有一个
{
x=++tot;//记录标号
fa[x]=s;//连到s下
key[x]=v;//保存这个数
sz[x]=num[x]=1;//子树大小和数据数都为1
son[x][0]=son[x][1]=0;//建上空的叶子节点
splay(x,0);//旋转至根节点
return;
}
if(num[x]>1)//此点数据数大于1 及是否存在
{
sz[x]++;
num[x]++;//子树大小和此点数据数都增加
splay(x,0);//旋转至根节点
return;
}
insert(son[x][v>key[x]],x,v);//如果前两个条件都不满足 则把它连上它的父亲 递归
update(x);//及时更新子树大小
}
int get(int v)//找到数值为v的树上结点标号
{
int x=rt;
while(key[x]!=v&&x)//从根开始
x=son[x][v>key[x]];//通过逼近 找到比v小的(即v的左右儿子)其父亲即为所求
return x;
}
void del(int x)//删除某一值
{
x=get(x);
if(!x)
return;
splay(x,0);//旋转至根节点
if(num[x]>1)
{
num[x]--;
sz[x]--;
return;
}//如果有很多 删去其中一个 并将num和sz更新
if(!son[x][0]||!son[x][1])//左右儿子其中一个不存在
rt=son[x][0]+son[x][1];//直接搞啊 反正一个是0 加上也无妨
else
{
int y=son[x][1];//右儿子
while(son[y][0])//如果有左儿子
y=son[y][0];//就一直向下 找到第一个比x大的数值所在节点
splay(y,x);//然后把y旋转至x
son[y][0]=son[x][0];//把x的左儿子给y
fa[son[x][0]]=y;//同上
rt=y;//把根搞成y
}
update(rt);//正经换根操作
fa[rt]=0;//同上
}
int rank(int v)//找到数值为v的节点的排名
{
int x=rt,ret=0;//此时先从根找 ret维护答案
while(x)//非虚根
{
if(v<=key[x])//如果此数值≤当前数值 那么将x变成它的左儿子 即向左搜
x=son[x][0];
else
{
ret+=sz[son[x][0]]+num[x];//排名即为左儿子子树大小和左儿子的大小之和
x=son[x][1];//然后把它变成自己的右儿子 即向右搜
}
}
return ret+1;//加上自己
}
int kth(int v)//查询排名为v的数
{
int x=rt;
while(v<=sz[son[x][0]]||v>sz[son[x][0]]+num[x])
{
if(v<=sz[son[x][0]])
x=son[x][0];
else//若大于 则减一下 然后把x移到左儿子处
v-=sz[son[x][0]]+num[x],x=son[x][1];
}
return key[x];
}
int pre(int v)//前驱 第一个比v小的数值所在节点的数值
{
int x=rt,ret=-0x7fffffff;//查询最小 因此此处赋值为负无穷
while(x)
{
if(v>key[x])
ret=max(ret,key[x]),x=son[x][1];//因为要在左子树一直向右下走 所以要取max
else
x=son[x][0];//否则自然要向左走
}
return ret;
}
int suf(int v)//后继 第一个比v大的数值所在节点的数值
{
int x=rt,ret=0x7fffffff;//查询最大 因此此处赋值为正无穷
while(x)
{
if(v<key[x])
ret=min(ret,key[x]),x=son[x][0];//因为要在右子树一直向左下走 所以要取min
else
x=son[x][1];//否则自然要向右走
}
return ret;
}
int n;
int main()
{
scanf("%d",&n);
while(n--)
{
int op,x;
scanf("%d%d",&op,&x);
if(op==1)
insert(rt,0,x);//加入
if(op==2)
del(x);//删除
if(op==3)
printf("%d\n",rank(x));
if(op==4)
printf("%d\n",kth(x));
if(op==5)
printf("%d\n",pre(x));
if(op==6)
printf("%d\n",suf(x));
}
}
讲解待补