splay的关键在于rotate函数和splay函数,其他的函数就是根据二叉平衡树的性质来写的
struct node
{
int w;//当前节点代表的权值
int fa;//当前节点的父亲节点
int son[2];//当前节点的左儿子,右儿子标号
int rec;//当前点代表权值在数列中重复次数
int numof_son;//当前点的子节点个数
}tree[maxn];
inline bool _pos(int _x)
{
return ((tree[tree[_x].fa].son[1]==_x)?1:0);//1是右儿子,0是左儿子
}
inline void update(int _x)
{
tree[_x].numof_son=tree[tree[_x].son[0]].numof_son+tree[tree[_x].son[1]].numof_son+tree[_x].rec;
}
我们不难发现,这个操作会涉及到4个点
当前节点为x
x的父节点为y
y的父节点为r
旋转时受到影响的x的子树w
将x向上转移一层,即:将r的某儿子变为x,将x的某儿子变为y,将y的某儿子变为w
但是具体怎么移动呢?
这里的关键是寻找到这些点在旋转之后的位置关系
根据二叉排序树的性质,我们模拟一下可以知道(模拟图见上),一共只有4种情况,而这所有的情况都满足如下规律:
1.y与r的位置关系和之后x与r的位置关系相同
2.变换后的w与y的位置关系与原来w和x的位置关系相反(即w是x的leftson,之后则w是y的rightson)
3.y与x的位置关系和之后x和y的位置关系相反
4.旋转时受到影响的x的子树w与x的位置关系和x与y的位置关系相反
5.旋转后的w和y的位置关系与之前x与y的位置关系相反
在实际操作中,可以画个图来看点的位置关系
inline void rotate(int _x)
{
int _y=tree[_x].fa;
int _r=tree[_y].fa;
int _pos_y=_pos(_y);
int _pos_x=_pos(_x);
int _change_other=tree[_x].son[_pos_x^1];
tree[_r].son[_pos_y]=_x;
tree[_x].fa=_r;
tree[_x].son[_pos_x^1]=_y;
tree[_y].fa=_x;
tree[_y].son[_pos_x]=_change_other;
tree[_change_other].fa=_y;
update(_y);update(_x);
}
根据tarjan巨佬大神犇的计算,我们不能直接暴力的将_x节点往上翻转,否则这颗树不再平衡,出题人会把它卡到爆的
比较优秀的做法是:
当目标和当前位置距离一层时,直接翻转_x就好
当_x和其父亲在同一条线上时(三点一线,grandfather,father,son),先旋父亲再旋自己
否则将_x向上旋转2次
(本函数里的第二个参数,当不传的时候默认为0 )
inline void splay(int _x,int _to=0)
{
while(tree[_x].fa!=_to)
{
int _y=tree[_x].fa,_z=tree[_y].fa;
if(_z!=_to)
{
if(_pos(_x)==_pos(_y))rotate(_y);
else rotate(_x);
}
rotate(_x);
}
if(_to==0)root=_x;
}
根据二分查找树的性质,我们从根节点开始,若当前节点权值大于_w,左转,反之右转
当退出第一个while循环时,有两种情况:1._w曾经被插入过 2._w还没有被插入过
由于在树中我们每个数只出现一次,重复的会被标记(即每个节点保存的是也许很多数)
所有对于情况1,我们直接对其标记累加就好
对于情况2.我们就要新建一个点
注意这个函数不要打错了
inline void insert(int _w)
{
int _cur=root,_p=0;
while(_cur&&tree[_cur].w!=_w)
{
_p=_cur;
_cur=tree[_cur].son[_w>tree[_cur].w];
}
if(_cur)tree[_cur].rec++;
else
{
_cur=++cnt;
tree[_cur].w=_w;
if(_p)tree[_p].son[_w>tree[_p].w]=_cur;
tree[_cur].fa=_p;
tree[cnt].numof_son=tree[cnt].rec=1;
tree[cnt].son[0]=tree[cnt].son[1]=0;
}
splay(_cur);
}
其实_w这个值不一定存在(大多数时候是存在的)
我们面临着两种情况:
当其存在时,就可以不断二分找到它然后把它旋到root
当其不存在时,考虑分析此函数的倒数第2步,此时会发现,有四种情况,且返回_w的前驱和后继的概率相等,于是此时可能返回前驱或后继
为了消除这个随机性的影响,我们就应该将找到的这个点旋到root,这给后面查找前驱或后继提供了便利
inline void find(int _w)
{
int _cur=root;
while(tree[_cur].w!=_w&&tree[_cur].son[_w>tree[_cur].w])_cur=tree[_cur].son[_w>tree[_cur].w];
splay(_cur);
}
从root开始
如果当前节点的左边的数字个数小于k,那么这个点一定在右边,右转
或者当左边节点小于k且当前节点的数的个数加上左边节点的数的个数大于等于k时,找到了_w的位置,返回
剩下的情况就是右转了
inline int findkth(int _k)
{
int _cur=root;
while(19260817)
{
if(_k<=tree[tree[_cur].son[0]].numof_son)_cur=tree[_cur].son[0];
else if(_k<=tree[tree[_cur].son[0]].numof_son+tree[_cur].rec)return _cur;
else _k-=tree[tree[_cur].son[0]].numof_son+tree[_cur].rec,_cur=tree[_cur].son[1];
}
}
先用find函数找,调用此函数后会有三种情况:root为_w本身,root为_w的前驱,root为_w的后继
这时就用
if(tree[root].w<_w)return root;
来处理root为_w的前驱的情况
后面的代码很好的处理了另外两种情况(可自行画图理解)
似 pre()
inline int pre(int _w)
{
find(_w);
if(tree[root].w<_w)return root;
int _cur=tree[root].son[0];
while(tree[_cur].son[1])_cur=tree[_cur].son[1];
return _cur;
}
inline int bac(int _w)
{
find(_w);
if(tree[root].w>_w)return root;
int _cur=tree[root].son[1];
while(tree[_cur].son[0])_cur=tree[_cur].son[0];
return _cur;
}
先把_w的前驱旋到root,再把_w的后继旋到其前驱的下面,
这个时候,根据树的性质,我们要del的数所在的节点一定是其后继的左儿子且没有子树(前驱后继之间只有_w一个数)
于是我们按照实际情况进行相关操作即可
attention:我们只是在逻辑上将点删除,在内存中这个点还是存在那里的,只是与其他点失去了联系罢了,这就可能造成空间上的浪费,比较好的解决办法就是用指针
inline void del(int _w)
{
int _front=pre(_w);int _back=bac(_w);
splay(_front);
splay(_back,_front);
int _del=tree[_back].son[0];
if(tree[_del].rec>1)tree[_del].rec--,splay(_del);
else tree[_back].son[0]=0;
}
由于_find()函数返回的可能是前驱或后继或是其本身,故在查找排名时应该分类讨论
若返回的是前驱,则返回当前的root的点数和root的左儿子的子树大小
若是后继或本身,那么就返回root左子树的大小
inline int getrank(int _w){
_find(_w);
if(tree[root].w<_w)return tree[tree[root].son[0]].siz+tree[root].cnt+1;
else return tree[tree[root].son[0]].siz+1;
}
int main(){
register int n;read(n);
register int opt,x;
in(INF);
in(-INF);
//技巧:先插入两个极大极小值,防止出现插入的数没有前驱和后继的情况
//否则若一个数没有后继,find()会出麻烦
loop(i,1,n){
read(opt);read(x);
if(opt==1)in(x);
else if(opt==2)del(x);
else if(opt==3)printf("%d\n",getrank(x)-1);
else if(opt==4)printf("%d\n",tree[_findKth(x+1)].w);
else if(opt==5)printf("%d\n",tree[pre(x)].w);
else if(opt==6)printf("%d\n",tree[bac(x)].w);
}
return 0;
}
关于splay的复杂度分析,本人表示无能为力,引援一篇blog
本人比较蒟,每次打个splay都要调试半天,特将关于splay的坑点枚举于此
一定要插入足够大和小的值,否则会出一些玄学错误,一定要能多大就多大
比如这个情况
rotate函数理论上来说有不同的写法,但是原则就是一定要把那四个点的父子关系和相关信息维护正确
比如:在维护x的父子关系时,要确定y是x的哪个儿子,理论上可以通过:
1.原来x相对于y的位置关系
2.x的被影响的那个子节点相对于x的位置关系
两种方式来判断
这时问题来了,如果x没有子节点呢,这时x的子节点就是0(空都是0),那么这样就没有办法正确的更新父子关系,所以以后还是用前一种方法吧
if(_p)tree[_p].son[_w>tree[_p].w]=_cur;
这一句特别容易被打掉!!!!!
如果不打的话,新插入的这个点的父节点就没法和这个点相连了
int _cur=root,_p=0;
while(_cur&&tree[_cur].w!=_w)
{
_p=_cur;
_cur=tree[_cur].son[_w>tree[_cur].w];
}
在寻找值为w的点时,while循环里面的判断条件是当前所在的点不为空!
这和find函数中的即将到达的点不为空需要区别
while(1){
if(tree[Lson(cur)].siz>=k)
cur=Lson(cur);
else if(tree[cur].cnt+tree[Lson(cur)].siz>=k){
Splay(cur);
return tree[cur].w;
}
else if(tree[cur].cnt+tree[Lson(cur)].siz<k){
k-=tree[cur].cnt+tree[Lson(cur)].siz;//
cur=Rson(cur);
}
}
注意第3个else if里面的两句话的顺序不可以换!
虽然很显然,但是在没有高度专注的情况下总是会出现这些神奇的情况
记清楚步骤:先旋前驱到root,再旋后继到其前驱的下面
不能把后继旋到根上去了
在大部分定义里面,数m的排名为小于m的数的个数+1,不能忘记+1!
有些函数返回值可以是某个点的值,也可以是某个点的编号
但是如果返回值,就不可以得到编号,若返回编号,却可以得到值
如果不做强行规定,在构建代码的时候会出现RE的风险
因此,规定如下:
由于结构体的特点,用结构体方式构建的Splay必然会显得比较冗杂,语句偏长容易积累bug且不容易调试,因此推荐以下宏定义:
#define f(x) tree[x].fa
#define Lson(x) tree[x].son[0]
#define Rson(x) tree[x].son[1]
#define ADX_son(x,w) tree[x].son[tree[x].w
//按大小的儿子~~
题解代码:
#include
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;++i)
#define ll long long
const int maxn=100000+10;
const int INF=1e9+7;
template<typename T>void read(T &x){
x=0;char r=getchar();T neg=1;
while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
while(r>='0'&&r<='9'){x=(x<<1)+(x<<3)+r-'0';r=getchar();}
x*=neg;
}
struct node{
int f;
int siz;
int cnt;
int son[2];
int w;
}tree[maxn];
int numofp=0,root=0;
inline int getpos(int x){return ((tree[tree[x].f].son[0]==x)?0:1);}
inline void update(int x){tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+tree[x].cnt;}
inline void Ro(int x){
int f=tree[x].f;
int ff=tree[f].f;
int s=tree[x].son[(getpos(x)==0)?1:0];
int posx=getpos(x);
int posf=getpos(f);
tree[f].son[posx]=s;
tree[s].f=f;
tree[ff].son[posf]=x;
tree[x].f=ff;
tree[x].son[posx^1]=f;
tree[f].f=x;//same!
update(f),update(x);
}
inline void Splay(int x,int to=0){
while(tree[x].f!=to){
int f=tree[x].f,ff=tree[f].f;
if(ff!=to){
if(getpos(f)==getpos(x))Ro(f);
else Ro(x);
}
Ro(x);
}
if(to==0)root=x;
}
inline void in(int w){
int _p=0;int cur=root;
while(cur&&tree[cur].w!=w)_p=cur,cur=tree[cur].son[w>tree[cur].w];
if(cur)tree[cur].cnt++;
else{
cur=++numofp;
tree[cur].cnt=1;
tree[cur].w=w;
tree[cur].siz=1;
tree[cur].son[1]=tree[numofp].son[0]=0;
tree[cur].f=_p;
if(_p)tree[_p].son[w>tree[_p].w]=cur;
}
Splay(cur);
}
inline void _find(int _w){
int cur=root;
while(tree[cur].w!=_w&&tree[cur].son[tree[cur].w<_w])
cur=tree[cur].son[tree[cur].w<_w];
Splay(cur);
}
inline int getrank(int _w){
_find(_w);
if(tree[root].w<_w)return tree[tree[root].son[0]].siz+tree[root].cnt+1;
else return tree[tree[root].son[0]].siz+1;
}
inline int _findKth(int _x){
int cur=root;
int _rank=_x;
while(1){
if(_rank<=tree[tree[cur].son[0]].siz)cur=tree[cur].son[0];
else if(_rank<=tree[tree[cur].son[0]].siz+tree[cur].cnt)return cur;
else _rank-=tree[tree[cur].son[0]].siz+tree[cur].cnt,cur=tree[cur].son[1];
}
}
inline int pre(int _w){
_find(_w);
if(tree[root].w<_w)return root;
int cur=tree[root].son[0];
while(tree[cur].son[1])cur=tree[cur].son[1];
return cur;
}
inline int bac(int _w){
_find(_w);
if(tree[root].w>_w)return root;
int cur=tree[root].son[1];
while(tree[cur].son[0])cur=tree[cur].son[0];
return cur;
}
inline void del(int _w){
int fr=pre(_w),ba=bac(_w);
Splay(fr);Splay(ba,fr);
int _del=tree[ba].son[0];
if(tree[_del].cnt>1)--tree[_del].cnt,Splay(_del);
else tree[ba].son[0]=0;
}
int main(){
register int n;read(n);
register int opt,x;
in(INF);
in(-INF);
loop(i,1,n){
read(opt);read(x);
if(opt==1)in(x);
else if(opt==2)del(x);
else if(opt==3)printf("%d\n",getrank(x)-1);
else if(opt==4)printf("%d\n",tree[_findKth(x+1)].w);
else if(opt==5)printf("%d\n",tree[pre(x)].w);
else if(opt==6)printf("%d\n",tree[bac(x)].w);
}
return 0;
}
题解
题解
题解