splay简介
splay是一颗bst,有“序列之王”的美誉,可以处理序列的一切问题。处理区间只有splay和fhq_treap两棵平衡树(当然,如果你想写,B+)可以解决。
优点:万能、常数较小、可用于link cut tree
缺点:代码长、不能可持久化。
相关文章:fhq_treap link cut tree
splay操作
void splay(x,goal)
我们假设有一棵原树,长成这样:
splay树满足如下的性质:中序遍历是有序的(当处理普通平衡树问题的时候,且合法的操作都符合特性)。
注意:上述提醒对于序列操作无效,只是当你推导有困难的时候自己画图(脑补)考虑一下。
通过将节点旋转至根节点来进一步操作(称为splay操作)
那么我们先来看splay操作:
第一种情况、需旋转的节点x的父亲是目标节点p。那么只要一次旋转。如图x是左儿子,需要右旋。图为将a旋转到b。图片来自百度
第二种情况、x、x的父亲、x的父亲的父亲“三点共线”(如果不能理解就看下面的图),执行两次相同方向的旋转。那么先旋转x的父节点,再旋转x。图为将x点旋转到z点。图片来自百度。
第三种情况、else。执行两次不同方向的旋转。图为将x旋转到g。
上述三种情况介绍完之后,相信同学们对于旋转(还不懂?往下翻)以及splay操作有一定的认识。
递归写法:(来源:算法竞赛入门经典)
void splay(Node* &o,int k) {
int d=o->cmp(k);
if (d==1) k-=o->ch[0]->s+1;
if (d!=-1) {
Node *p=o->ch[d];
int d2=p->cmp(k);
int k2=(d2==0>k:k-p->ch[0]->s-1);
if (d2!=-1) {
splay(p->ch[d2],k2);
if (d==d2) rotate(o,d^1); else rotate(o->ch[d],d);
}
}
rotate(o,d^1);
}
非递归写法(建议同学们背一下):
void splay(int x,int goal=0) {
for (int f;(f=fa[x])!=goal;rotate(x))
if (fa[f]!=goal) rotate((get(x)==get(f))?f:x);
if (!goal) rt=x;
}
void rotate(x)
就是上述过程中的旋转。需要完成下面4件事情:
X变到原来Y的位置
Y变成了 X原来在Y的 相对的那个儿子
Y的非X的儿子不变 X的 X原来在Y的 那个儿子不变
X的 X原来在Y的 相对的 那个儿子 变成了 Y原来是X的那个儿子
上代码。
bool get(int x) {return ch[fa[x]][1]==x;}
void rotate(int x) {
int y=fa[x],z=fa[y],k=get(x);
//y是x的父亲,z是父亲的父亲,k是x是否是它父亲的右子树。
pushdown(y); pushdown(x); //不要忘记pushdown
ch[y][k]=ch[x][k^1]; fa[ch[y][k]]=y;
ch[x][k^1]=y; fa[y]=x;
fa[x]=z;
if (z) ch[z][ch[z][1]==y]=x;
pushup(y); pushup(x); //pushup
}
未完待续