老习惯,不说了
(正文中有个Splay 快速入门,博主认为写得很好,有暂时还不回Splay的萌新可以去学一下哦!)
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)输入输出格式
输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号( 1≤opt≤6 )
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
输入输出样例
输入样例#1:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例#1:
106465
84185
492737
说明
时空限制:1000ms,128M
1.n的数据范围: n≤100000 n \leq 100000 n≤100000
2.每个数的数据范围: [−107,107][-{10}^7, {10}^7][−107,107]
控制不住自己说点心路历程,博主学了两个晚上终于把Splay给学会了,学的博主真是欲仙欲死,不过还好学会就好,其实这应该是一道平衡树的题目的,只不过被我们强行用Splay给A了,推荐大家去学一学Treap吧(看看博主的也不错哦)
在此如果大家有任何疑问的话,可以与博主讨论,在此给大家安利一个博主是从哪里学来的博客(Splay快速入门),相信大家看了之后,画一画就可以弄懂Splay的基础操作了!
好了,话不多说看代码,代码里有详细的注释,不懂一定要发讨论这不仅是你的进步,也是大家的进步!(在此博主谢谢大家了->->)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int _=1e5+5;
int root,n,tot;
struct hand
{
int kid[2];//左右儿子
int dad;//父节点
int cnt;//数量
int val;//值
int size;//儿子数量
}tr[_<<2];
inline void js(int u)//统计儿子数
{
tr[u].size=tr[tr[u].kid[0]].size+tr[tr[u].kid[1]].size+tr[u].cnt;
}
void rotate(int x)//旋转
{
int y=tr[x].dad;
int z=tr[y].dad;
int k=tr[y].kid[1]==x;//x是y的左或右儿子
tr[z].kid[tr[z].kid[1]==y]=x;//把x放在y的原位置上
tr[x].dad=z;//换一下x的父亲
tr[y].kid[k]=tr[x].kid[k^1];//把原本属于x的儿子且不同于x属于y的儿子方向的儿子赋在该属于x属于y的位置上
tr[tr[x].kid[k^1]].dad=y;//换一下上一行修改的那个点的父亲
tr[x].kid[k^1]=y;//把y放在x的原位置上
tr[y].dad=x;
//注意原本属于x的儿子且属于x属于y方向的儿子是不需要动的!!!还有一个点也是不要动的
js(y);js(x);//所以说顺序不能换,换了的话x的size就变了
}
//若stand是虚根,则把x转到根节点上,否则转到Stand的儿子上
void Splay(int x,int Stand)
{
while(tr[x].dad!=Stand)
{
int y=tr[x].dad,z=tr[y].dad;
if(z!=Stand)
{
(tr[z].kid[0]==y)^(tr[y].kid[0]==x)?rotate(x):rotate(y);
}
/*
6种情况下合并为三种
1 1 1 1 1 1
/ \ / \ / \
2 2 2 2 2 2
/ \ \ / y已是根节点,只能转x
3 3 3 3
(Num1) (Num2) (Num3) (Num4)
先转y再x 先转x再x
*/
//由此可见,x必须转
rotate(x);
}
if(!Stand)root=x;
}
void Insert(int x)
{
int u=root,dad=0;
while(u&&tr[u].val!=x)//当u存在并且没有移动到到目前的值
{
dad=u;
u=tr[u].kid[x>tr[u].val];//二分查找
}
//把x要放的位置找到
if(u)tr[u].cnt++;//x已经存在过了
else
{
u=++tot;//新节点的位置
if(dad)//如果父节点非根
tr[dad].kid[x>tr[dad].val]=u;
tr[tot].kid[0]=tr[tot].kid[1]=0;//不存在儿子
tr[tot].dad=dad;tr[tot].val=x;
tr[tot].cnt=1;tr[tot].size=1;
}//OK了,要插入的点已经进去了
Splay(u,0);//记得操作后要保证平衡哦
}
void Find(int x)//查找x的位置,并将其旋转到根节点
{
int u=root;
if(!u)return;//不存在节点,无法查找
while(tr[u].kid[x>tr[u].val]&&x!=tr[u].val)//当存在x且当前位置的x值不为1
u=tr[u].kid[x>tr[u].val];//跳到儿子去咯
Splay(u,0);//把当前位置转移到根节点
}//SYC说Splay每次操作都要把x转到根节点上去
//所以是棵动态树,神奇!
int Next(int x,int op)//查找前驱/后继
{
Find(x);
int u=root;
//op==1为后继,op==0为前驱
if((tr[u].val>x&&op)||(tr[u].val<x&&!op))return u;
u=tr[u].kid[op];
while(tr[u].kid[op^1])u=tr[u].kid[op^1];
//若要找后继,则先跳右节点,然后一直跳左跳到跳不动为止,前驱相反
return u;
}
void Delete(int x)
{
int last=Next(x,0);//查找前驱
int next=Next(x,1);//查找后继
Splay(last,0);Splay(next,last);
//将前驱旋到根节点上,r然后将后继旋到前驱的儿子下,因此后继成了前驱的右儿子,x是后继的左儿子且儿子为叶子节点
int del=tr[next].kid[0];
if(tr[del].cnt>1)
{
tr[del].cnt--;
Splay(del,0);//别忘记旋到根节点上去哦
}
else
tr[next].kid[0]=0;//清除该节点
}
int K_th(int x)
{
int u=root;
if(tr[u].size<x)return 0;//都不存在这么多数
while(1)
{
int y=tr[u].kid[0];
if(x>tr[y].size+tr[u].cnt)//排名在u的后面,在右子树中找
{
x-=tr[y].size+tr[u].cnt;
u=tr[u].kid[1];
}
else if(tr[y].size>=x)//排名在u的前面,在左子树中找
u=y;
else return tr[u].val;
}
}
int main()
{
Insert(-123456789);//处理前驱
Insert(123456789);//处理后继
//这样就不用处理莫名其妙的细节咯
scanf("%d",&n);
while(n--)
{
int op,x;
scanf("%d",&op);
scanf("%d",&x);
if(op==1)Insert(x);
if(op==2)Delete(x);
if(op==3)
{
Find(x);
printf("%d\n",tr[tr[root].kid[0]].size);
}
if(op==4)
{
printf("%d\n",K_th(x+1));
}
if(op==5)
{
printf("%d\n",tr[Next(x,0)].val);
}
if(op==6)
{
printf("%d\n",tr[Next(x,1)].val);
}
}
return 0;
}
Thanks for your attention.