@(splay入门详解)
引入
splay是一种BST,可以维护静态区间k小
也可以当作区间树,维护区间信息( 求和,最大子段和,翻转区间,等等)
时间一般O(nlogn)(均摊),splay操作满足其稳定性
但是常数巨大,接近100,慎用
splay的构建
一个完整的splay包含以下变量:
- fa:此节点的父亲
- val:此点的权值
- cnt:此权值的数量(区间树中则不需要)
- ch[2]:此点的儿子
- sz:它以及儿子的大小
- tag:lazytag(如果有需要)
接下来是一些操作
基础操作
建树
可以一个一个insert,也可以直接构造满二叉树
int build(int l,int r,int f){
if(l>r) return 0;
int mid=((l+r)>>1),x;
if(top) x=rb[top--];
else x=++tot;
t[x].fa=f;
t[x].val=a[mid];
t[x].rev=t[x].laz=0;
t[x].ls=t[x].rs=Max(a[mid],0);
t[x].ch[0]=build(l,mid-1,x);
t[x].ch[1]=build(mid+1,r,x);
push_up(x);
return x;
}
get
获取此点在父亲那里的位置
int get(int x){
return (t[t[x].fa].ch[1]==x);
}
rotate
splay基础操作,把两个点旋转之后却不能破坏BST条件
有一个规律:此点的在(父亲在祖父那里的位置)的位置的儿子不会变
void rotate(int x){
int f=t[x].fa;int g=t[f].fa;
int kx=get(x),kf=get(f);
t[t[x].ch[kx^1]].fa=f;
t[f].ch[kx]=t[x].ch[kx^1];//不满足条件的儿子转到原来此点在父亲那里的地方
t[f].fa=x;
t[x].ch[kx^1]=f;//父亲变成此点的儿子
t[x].fa=g;
t[g].ch[kf]=x;//此点变成祖父的儿子
push_up(f);
push_up(x);
}
splay
核心操作,把一个点旋转到根,确保平衡性,在修改操作之后
一般用双旋满足平衡性
对于不同父亲和儿子的相对位置,有以下两种情况:
第一种先转父亲,第二种先转儿子
void splay(int x,int v){
while(t[x].fa!=v){
if(t[t[x].fa].fa!=v){ //注意别转多了
rotate(get(x)==get(t[x].fa)?t[x].fa:x);// 如果get(x)==get(fa) 就旋转父亲,否则旋转儿子
}
rotate(x);//第二下一定是旋转x
}
if(!v) rt=x;//换根
}
insert
我们要在pos处加入len个数,插入之后还要满足平衡树平衡(中序遍历要严格单调)的条件,应该怎么插入呢?
把pos splay到根,再把pos+1 splay到根的右儿子处,那么pos+1的点的左儿子就一定是pos到pos+1之间的数,也就是空的
那么在这时把要插入的区间构成一颗满二叉树,再把根和pos+1连接,就完成了插入
插入完成之后记得push_up
要更新信息
void insert(int l,int len){
int r=l+1;
l=kth(l+1);r=kth(r+1);
splay(l,0);splay(r,l);//提取区间
for(int i=1;i<=len;i++){
a[i]=read();
}
t[r].ch[0]=build(1,len,r);
n+=len;
push_up(r);push_up(l);//先pushr,因为在下面
}
kth
重点操作,查询第k大,用splayBST的性质搜索即可
查询到一个点时,可分为几个区间
① k <= 左子树size,直接递归左子树
② 左子树size < k <= 左size+cnt[now],当前值!
⑨ k > 左size+cnt[now] 先减去左size+cnt[now],再递归右子树
区间k大的话可以先提取区间!
int kth(int k){
int x=rt;
while(1){
push_down(x);
if(k<=t[t[x].ch[0]].sz){
x=t[x].ch[0];
}else if(k==t[t[x].ch[0]].sz+1){
return x;
}else{
k-=t[t[x].ch[0]].sz+1;
x=t[x].ch[1];
}
}
}
delete
删除此点,提取区间后断开联系就可以,注意有多个的情况要--cnt,不断联系
区间树:
void recycle(int x){
if(!x) return;
rb[++top]=x;
recycle(t[x].ch[0]);
recycle(t[x].ch[1]);
}
void del(int l,int r){
n-=r-l+1;
l=kth(l);r=kth(r+2);
splay(l,0);splay(r,l);
recycle(t[r].ch[0]);//recycle是内存回收
t[r].ch[0]=0;
push_up(r);
push_up(l);
}
权值BST:
inline void del(long long key){
long long pr=pre(key),su=suc(key);
splay(pr,0);
splay(su,pr);
long long u=ch[su][0];
//把key的前驱伸展到rt,后继伸展到rt的右儿子
//那么key一定是后继的左儿子,而且没有儿子
if(cnt[u]>1){
cnt[u]--;
splay(u,0);
}else{
ch[su][0]=0;
splay(su,0);
cnt[u]=0;
}
}
find
把距这个值最近的值splay到根
inline void find(long long key){
long long u=rt;
while(val[u]!=key&&ch[u][key>val[u]]) u=ch[u][key>val[u]];
splay(u,0);
}
pre
求前驱,先find
然后检查这个值在它前面还是在后面
在前面一定是前驱
在后面的话,一定是根左子树中最大的
inline long long pre(long long key){
find(key);
if(val[rt]<key) return rt;
long long u=ch[rt][0];
while(ch[u][1]){
u=ch[u][1];
}
return u;
}
suc
求后继,同pre
inline long long suc(long long key){
find(key);
if(val[rt]>key) return rt;
long long u=ch[rt][1];
while(ch[u][0]){
u=ch[u][0];
}
return u;
}
基础操作就先这么多了,之后再更新进阶操作
注意事项
- 要多splay
- delete讨论cnt>1?
- insert更新信息,要找是否已有此节点
rotate注意顺序和方向,要push_up
splay应用
基础运用:luogup3369普通平衡树
#include<iostream>
#include<cstdio>
using namespace std;
long long read(){
char ch=getchar();long long x=0,pos=1;
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
return pos?x:-x;
}
const long long maxn=1000111;
long long n,fa[maxn],val[maxn],cnt[maxn],ch[maxn][3],sz[maxn],rt,tot;
inline long long get(long long u){
return (ch[fa[u]][1]==u);
}
inline void push_up(long long u){
sz[u]=sz[ch[u][0]]+sz[ch[u][1]]+cnt[u];
}
inline void rotate(long long u){
long long f=fa[u];long long g=fa[f];long long k=get(u);
fa[u]=g;
ch[g][get(f)]=u;
fa[ch[u][k^1]]=f;
ch[f][k]=ch[u][k^1];
fa[f]=u;
ch[u][k^1]=f;
push_up(f);
push_up(u); // ※※※
}
inline void splay(long long u,long long v){
while(fa[u]!=v){
long long f=fa[u];
if(fa[f]!=v){
rotate((get(f)==get(u)?f:u));
}
rotate(u);
}
if(!v) rt=u;
}
inline void insert(long long key){
long long u=rt,p=0;
while(u&&val[u]!=key){
p=u;
u=ch[u][key>val[u]];
}
if(u) ++cnt[u];
else{
u=++tot;
val[u]=key;
if(p){
ch[p][key>val[p]]=u;
}
sz[u]=1;
fa[u]=p;
cnt[u]=1;
ch[u][0]=ch[u][1]=0;
}
splay(u,0);
}
inline void find(long long key){
long long u=rt;
while(val[u]!=key&&ch[u][key>val[u]]) u=ch[u][key>val[u]];
splay(u,0);
}
inline long long rnk(long long key){
find(key);
return sz[ch[rt][0]];//有-inf这个点,不用加一
}
inline long long kth(long long k){
++k;// -inf
long long u=rt;
while(1926){
if(sz[ch[u][0]]+cnt[u]<k) k-=(sz[ch[u][0]]+cnt[u]),u=ch[u][1];
else if(k<=sz[ch[u][0]]) u=ch[u][0];
else return val[u];
}
//k < sz[ch[u][0]]: 在左子树中
//sz[ch[u][0]] < k <= sz[ch[u][0]]+cnt[u] 为当前值
//否则是右子树中
}
inline long long pre(long long key){
find(key);
if(val[rt]<key) return rt;
long long u=ch[rt][0];
while(ch[u][1]){
u=ch[u][1];
}
return u;
}
inline long long suc(long long key){
find(key);
if(val[rt]>key) return rt;
long long u=ch[rt][1];
while(ch[u][0]){
u=ch[u][0];
}
return u;
}
inline void del(long long key){
long long pr=pre(key),su=suc(key);
splay(pr,0);
splay(su,pr);
long long u=ch[su][0];
//把key的前驱伸展到rt,后继伸展到rt的右儿子
//那么key一定是后继的左儿子,而且没有儿子
if(cnt[u]>1){
cnt[u]--;
splay(u,0);
}else{
ch[su][0]=0;
splay(su,0);
cnt[u]=0;
}
}
int main(){
n=read();
insert(-192608170);
insert(192608170);
long long x,opt;
for(long long i=1;i<=n;i++){
opt=read();x=read();
if(opt==1){
insert(x);
continue;
}
if(opt==2){
del(x);
continue;
}
if(opt==3){
printf("%lld\n",rnk(x));
continue;
}
if(opt==4){
printf("%lld\n",kth(x));
continue;
}
if(opt==5){
printf("%lld\n",val[pre(x)]);
continue;
}
if(opt==6){
printf("%lld\n",val[suc(x)]);
continue;
}
}
return 0;
}