Splay学习笔记
这篇文章是写给自己看的,可能不会在意某些细节
参考博客(关键是有图好理解):https://www.cnblogs.com/cjyyb/p/7499020.html
前言
Splay是平衡树的一种
它跟treap不同的是,它可以智能(不需要去刻意找方向)旋转,也可以实现区间翻转,个人感觉也好写好调一些
是LCT的前置芝士
实现
代码小技巧
#define check(x,y) (a[y].ch[1]==x)
//判断x是y的哪个儿子
#define ls(x) a[x].ch[0]
#define rs(x) a[x].ch[1]
#define pushup(x) a[x].siz=a[x].cnt+a[ls(x)].siz+a[rs(x)].siz
插入
找到x的位置并插入
in void insert(int x)
{
int k=root,ff=0;
while(a[k].ch[(a[k].w<x)] && a[k].w!=x) ff=k,k=a[k].ch[(a[k].w<x)];//找到待插入值的位置 ,并记录改点父亲
if(k) a[k].cnt++,a[k].siz++; //若已有之前该点
else //新建节点
{
k=++tot;
if(ff) a[ff].ch[a[ff].w<x]=k;
a[k].cnt=a[k].siz=1;
a[k].w=x,a[k].ff=ff;
}
Splay(k,0);
}
单点旋转至上一层
旋转时一定要注意更新顺序!!!
in void rotate(int x) //将x向上旋转一层 (更新的顺序很重要!!!)
{
int y=a[x].ff; //x的父亲
int z=a[y].ff; //x的爷爷
int k=check(x,y); //x与y的方向关系
a[z].ch[check(y,z)]=x,a[x].ff=z;//先处理x的爷爷
a[a[x].ch[!k]].ff=y,a[y].ch[k]=a[x].ch[!k];//在建立x的另一侧儿子与x父亲的联系
a[x].ch[!k]=y,a[y].ff=x; //重新联系x,y
pushup(y),pushup(x);//因为y是x的儿子了,要先更新y
}
将x旋转至d的儿子的位置(Splay灵魂)
旋转时要根据当前点与其父亲、爷爷的关系调整旋转顺序保证树高
in void Splay(int x,int d)
{
while(a[x].ff!=d)
{
int y=a[x].ff;
int z=a[y].ff;
if(z!=d)
(check(x,y) ^ check(y,z) ? rotate(x) : rotate(y));
//如果x,y,z在三点不是一条直线则先旋转x ,不然先旋y(这样可以保证复杂度)
rotate(x); // 肯定还要旋一次x
}
if(d==0) root=x; //更新root
}
然后就是一些其他运用,在完整代码中提到
完整代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
int t=0; char ch=get;
while(ch<'0' || ch>'9') ch=get;
while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
return t;
}
const int _=1e5+4;
struct yzhx{
int ch[2],cnt,siz,ff,w;
}a[_];
int n,root,tot;
#define check(x,y) (a[y].ch[1]==x)
#define ls(x) a[x].ch[0]
#define rs(x) a[x].ch[1]
#define pushup(x) a[x].siz=a[x].cnt+a[ls(x)].siz+a[rs(x)].siz
in void rotate(int x) //将x向上旋转一层 (更新的顺序很重要!!!)
{
int y=a[x].ff; //x的父亲
int z=a[y].ff; //x的爷爷
int k=check(x,y); //x与y的方向关系
a[z].ch[check(y,z)]=x,a[x].ff=z;//先处理x的爷爷
a[a[x].ch[!k]].ff=y,a[y].ch[k]=a[x].ch[!k];//在建立x的另一侧儿子与x父亲的联系
a[x].ch[!k]=y,a[y].ff=x; //重新联系x,y
pushup(y),pushup(x);//因为y是x的儿子了,要先更新y
}
in void Splay(int x,int d)
{
while(a[x].ff!=d)
{
int y=a[x].ff;
int z=a[y].ff;
if(z!=d)
(check(x,y) ^ check(y,z) ? rotate(x) : rotate(y));
//如果x,y,z在三点不是一条直线则先旋转x ,不然先旋y(这样可以保证复杂度)
rotate(x); // 肯定还要旋一次x
}
if(d==0) root=x; //更新root
}
in void insert(int x)
{
int k=root,ff=0;
while(k && a[k].w!=x) ff=k,k=a[k].ch[(a[k].w<x)];//找到待插入值的位置 ,并记录改点父亲
if(k) a[k].cnt++,a[k].siz++; //若已有之前该点
else //新建节点
{
k=++tot;
if(ff) a[ff].ch[a[ff].w<x]=k;
a[k].cnt=a[k].siz=1;
a[k].w=x,a[k].ff=ff;
}
Splay(k,0);
}
in void find(int x) //找到x(或是最接近x的值),并将其旋转至根
{
int k=root;
while(a[k].ch[a[k].w<x] && a[k].w!=x) k=a[k].ch[a[k].w<x];
Splay(k,0);
return;
}
in int kth(int x)//从小到大查询排名为x的数
{
int k=root;
while(1)
{
if(a[ls(k)].siz>x) k=ls(k); //询问的点在当前点的左子树
else
{
if(a[ls(k)].siz+a[x].cnt>=x) return k; //找到了
else x-=a[ls(k)].siz+a[x].cnt, k=rs(k); //询问的点在当前点的左子树
}
}
return 0;
}
in int Next(int x,int f)//f==1是找后继,f==0找前驱
{
find(x);
int k=root;
if(a[k].w>x && f)return k;
if(a[k].w<x && !f) return k;
k=a[k].ch[f]; //先走到左子树or右子树,确保整棵子树都满足要求
while(a[k].ch[1^f]) k=a[k].ch[1^f]; //找当前子树内最大 or最小的
return k;
}
in void del(int x)
{
int l=Next(x,0),r=Next(x,1);
Splay(l,0),Splay(r,l);//先找到前驱和后继,并旋转
int k=ls(r);//此时r的后继左子树一定只有一个点,就是x
if(a[k].cnt>1)
{
a[k].cnt--,a[k].siz--;
Splay(k,0);
}
else a[r].ch[0]=0;
}
int main()
{
insert(-(0x3f3f3f3f)),insert(0x3f3f3f3f);//插入inf与-inf
n=read();
for(re int i=1;i<=n;i++)
insert(read());
}
嗯,就这样了...