Splay 学习笔记
鲜花
410里一位玩原神的主播说过:
"先学Splay,还能LCT。"
然而我学完了带旋treap和fhq-treap才来学splay
其实看来平衡树先学splay也是不错的选择。
准备
这部分很简单,相信都能看懂吧。
int val[N],son[N][2],fa[N],sum[N],cnt[N],rt,num;
#define ls(k) son[(k)][0]
#define rs(k) son[(k)][1]
inl int newnode(int x){
val[++num]=x;sum[num]=cnt[num]=1;
return num;
}
inl void pushup(int k){sum[k]=sum[ls(k)]+cnt[k]+sum[rs(k)];}
inl int getid(int k){return rs(fa[k])==k;}
rotate
和treap转的方式几乎一样
废话都是BST还能咋转
但二者底层逻辑不同
treap旋转是为了把点转到最底下删了或者维护堆性质
但splay是为了把点转到最顶上
三步概括一下:
假设要把b转上去,f为原A的父亲(左旋右旋不用管,这是treap的操作)
1.把b点右儿子e接到父亲a左儿子
2.把父亲a接到b右儿子
3.把b接到f
最后记得pushup
inl void rotate(int k){
int f=fa[k],g=fa[f],id=getid(k),gid=getid(f);
son[f][id]=son[k][id^1];fa[son[k][id^1]]=f;//1
son[k][id^1]=f;fa[f]=k;//2
if(g)son[g][gid]=k;//3
fa[k]=g;//可能当前换到根,fa也应清0,所以祖父有没有都要赋值
pushup(f),pushup(k);
}
splay
保证复杂度的重要操作(需要势能分析,但我不会qwq)
如果g、f、x在一条线上 先转f再转x可以让链长减半
然后就没了
inl void splay(int x){
for(int f;f=fa[x];rotate(x)){
if(fa[f])rotate(getid(f)^getid(x)?x:f);
}
rt=x;
}
delete
对于treap 我们会把点旋转到叶子再删
那么我们可以用类似fhq的操作 删完合并一下
inl void join(int x,int y){
int p=x;
while(rs(p))p=rs(p);
splay(p);
rs(rt)=y,fa[y]=rt;
pushup(rt);
}
inl void del(int x){
find(x);
if(cnt[rt]>1)return sum[rt]--,cnt[rt]--,void();
fa[ls(rt)]=fa[rs(rt)]=0;
if(!ls(rt))return rt=rs(rt),void();
if(!rs(rt))return rt=ls(rt),void();
join(ls(rt),rs(rt));
}
其余操作
按正常平衡树写 注意对每个点操作完后splay一下
inl void ins(int x){
if(!rt)return rt=newnode(x),void();
int p=rt,f=0;
while(1){
if(val[p]==x){
cnt[p]++;
break;
}
f=p,p=son[p][x>val[p]];
if(!p){
p=newnode(x);
fa[p]=f,son[f][x>val[f]]=p;
break;
}
}
pushup(p),pushup(f);
splay(p);
}