Luogu P3369 【模板】普通平衡树
啊 splay啊
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
//左儿子一定比我小 右儿子一定比我大
/* * <----0
|
* <----rt
/ \
* *
/ \ / \
* * * * 这是一棵树
*/
struct nod1{int ch[2],c,fa,size,cnt;}tr[110010];
//ch[0]是左儿子编号 ch[1]是右儿子比那好
//c是节点数值 fa是节点爸爸 size是以节点为根子树大小
//cnt是和节点数值一样的数的个数
int n,rt,tot;
//n是操作数 rt是根 tot是整个数的节点个数,添加新节点时用到
void update(int x)//更新维护 此处x是编号
{
tr[x].size=tr[tr[x].ch[0]].size+tr[x].cnt+tr[tr[x].ch[1]].size;
//x子树大小=x左子树+x右子树+x节点相同数个数
return ;
}//ok
int findip(int x)//找x数值的节点编号 此处x是数值
{
int now=rt;//从根开始找
while(x!=tr[now].c)
{
if(x<tr[now].c)//比当前点数值小
{
if(tr[now].ch[0]==0)break;//没有,只好跳出循环 返回当前这个now 是与x最贴切的节点
now=tr[now].ch[0];//走左边
}
else
{
if(tr[now].ch[1]==0)break;
now=tr[now].ch[1];//走右边
}
}
return now;
}
void add(int c,int x)//添加新节点 此处c是数值,x是新节点爸爸的编号
{
tot++;
tr[tot].c=c;tr[tot].cnt=1;
tr[tot].size=1;tr[tot].fa=x;
tr[tot].ch[0]=tr[tot].ch[1]=0;
if(c<tr[x].c)tr[x].ch[0]=tot;
else tr[x].ch[1]=tot;//判断新节点是爸爸的左儿子还是右儿子
return ;
}
void rotate(int x,int w)//单旋 此处x是编号 w是儿子标记 w等于1意味着我是我爸的左儿子,否则为我爸的右儿子
{
int f=tr[x].fa,ff=tr[f].fa;
tr[f].ch[1-w]=tr[x].ch[w];
//这里的ch[0]是左儿子,ch[1]是右儿子
//如果我是我爸的右儿子,那么说明我的整棵子树都比他大,所以我的左儿子给我爸当右儿子
//如果我是我爸的左儿子,那么说明我的整棵子树都比他小,所以我的右儿子给我爸当左儿子
if(tr[x].ch[w]!=0)tr[tr[x].ch[w]].fa=f;
if(tr[ff].ch[0]==f)tr[ff].ch[0]=x;//爷爷>爸爸
else tr[ff].ch[1]=x;//爸爸是爷爷什么儿子 我旋转后就给爷爷当什么儿子
tr[x].fa=ff;
tr[f].fa=x;
tr[x].ch[w]=f;
update(f);//更新底层
update(x);//更新当前层
}
void splay(int x,int goal)//把x旋到目标节点下面
{
while(tr[x].fa!=goal)
{
int f=tr[x].fa,ff=tr[f].fa;
if(ff==goal)//爷爷就是目标
{//旋一次就行
if(x==tr[f].ch[0])//我<我爸
rotate(x,1);
else rotate(x,0);
}
else
{
if(tr[ff].ch[0]==f)//爷爷>我爸
{
if(tr[f].ch[0]==x)//我爸>我
{//我与我爸共线 则双旋
rotate(f,1);//我爸是我爷爷左儿子
rotate(x,1);//我是我爸左儿子
}
else
{//我爸<我
rotate(x,0);//我是我爸右儿子
rotate(x,1);//现在我是我爷爷左儿子
}
}
else//爷爷<我爸
{
if(tr[f].ch[0]==x)
{
rotate(x,1);//我是我爸左儿子
rotate(x,0);//现在我是我爷爷右儿子
}
else
{//我与我爸共线 则双旋
rotate(f,0);//我爸是我爷爷右儿子
rotate(x,0);//我是我爸右儿子
}
}
}
}
if(goal==0)rt=x;
}
void insert(int x)//插入一个数 此处x是数值
{
if(rt==0)
{//第一个节点 先让你当0儿子
add(x,0);
rt=tot;
return ;
}
int ip=findip(x);//找出x数值的节点编号或是最贴近的节点
if(tr[ip].c==x)
{//之前插入过这个数值
tr[ip].cnt++;
update(ip);
splay(ip,0);
}
else
{
add(x,ip);//新点
update(ip);
splay(tot,0);
}
}
void del(int x)//删除一个数 此处x是数值
{
int ip=findip(x);//拿它编号
splay(ip,0);//让它当根 方便操作
if(tr[ip].c!=x)return ;//之前没有插入过这个数值
if(tr[ip].cnt>1)
{//不止一个这个数值
tr[ip].cnt--;
update(ip);
}
else if(tr[ip].ch[0]==0&&tr[ip].ch[1]==0)//已经将这个点旋到根了 如果没有左右儿子 说明整棵树只有一个点
rt=0,tot=0;
else if(tr[ip].ch[0]!=0&&tr[ip].ch[1]==0)//只有一个儿子就让他继承
rt=tr[ip].ch[0],tr[tr[ip].ch[0]].fa=0;
else if(tr[ip].ch[1]!=0&&tr[ip].ch[0]==0)//只有一个儿子就让他继承
rt=tr[ip].ch[1],tr[tr[ip].ch[1]].fa=0;
else
{
int p=tr[ip].ch[0];
while(tr[p].ch[1]!=0)p=tr[p].ch[1];//找前驱来
splay(p,ip);//旋到我这里来,因为前驱是小于我中最大的 它此时没有右儿子
rt=p;tr[p].fa=0;
tr[tr[ip].ch[1]].fa=p;//直接把右儿子给它
tr[p].ch[1]=tr[ip].ch[1];
update(p);
}
}
int rank(int x)//此处x是数值 找出x的排名
{
int ip=findip(x);splay(ip,0);//把我旋成根 方便处理
return tr[tr[ip].ch[0]].size+1;//我是根 所以左子树全小于我 +1就是我排名
}
int kth(int x)//找排名是x的数值
{
int now=rt;
while(1)
{
if(x<=tr[tr[now].ch[0]].size)//如果我小于左边的个数 去左边找
now=tr[now].ch[0];
else if(tr[tr[now].ch[0]].size+tr[now].cnt<x)
{//如果比左+当前自己还大 去右边
x=x-tr[tr[now].ch[0]].size-tr[now].cnt;
now=tr[now].ch[1];
}
else break;//等于当前
}
return tr[now].c;
}
int last(int x)//找前驱 此处x是数值
{
int ip=findip(x);
splay(ip,0);
if(x<=tr[ip].c&&tr[ip].ch[0]!=0)
{
ip=tr[ip].ch[0];
while(tr[ip].ch[1]!=0)ip=tr[ip].ch[1];
} //比我小最大的:左子树最右的
if(x<=tr[ip].c)return 0;
return tr[ip].c;
}
int next(int x)//找后继 此处x是数值
{
int ip=findip(x);
splay(ip,0);
if(x>=tr[ip].c&&tr[ip].ch[1]!=0)
{
ip=tr[ip].ch[1];
while(tr[ip].ch[0]!=0)ip=tr[ip].ch[0];
} //比我大最小的:右子树最左边
if(x>=tr[ip].c)return 0;
return tr[ip].c;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int h,x;
scanf("%d %d",&h,&x);
if(h==1)
insert(x);
else if(h==2)
del(x);
else if(h==3)
printf("%d\n",rank(x));
else if(h==4)
printf("%d\n",kth(x));
else if(h==5)
printf("%d\n",last(x));
else if(h==6)
printf("%d\n",next(x));
}
}
看了好多博客 要昏了