splay学习笔记

很久很久以前学过,但是现在都忘了

rotate

具体来说我们的 \(splay\) 要保证中序遍历不变
也很容易理解
这个有图
然后为了左旋右旋分开来写,我们把代码简化一下
我们的旋转是把 \(x\) 旋上去,这个之和 \(x\)\(y\) 的哪个儿子有关
然后之和 \(4\) 个点有关
\(get(x)\)\(x\)\(y\) 的那个儿子
然后注意代码顺序

v[fa[y]][get(y)]=x;
fa[y]=x;
v[y][get(x)]=v[x][!get(x)]
fa[v[x][!get(x)]]=y
v[x][!get(x)]=y
fa[x]=fa[y];

结束
看看tj

void rotate(int x){
    bool f=get(x);
    int y=fa[x],z=fa[y];
    v[z][get(y)]=x;
    fa[y]=x;
    v[y][f]=v[x][!f];
    fa[v[y][f]]=y;
    v[x][!f]=y;
    fa[x]=z;
    update(x),update(y);
}

splay

\(splay\) 每次操作后要将操作点旋转到根
这个东西要保证 \(splay\) 不会退化(也就是链长变成原来的一半)
所以我们如果 \(fa,x,fa[fa]\),三点共线,那么就先 \(splay\) \(fa\),再 \(splay\) \(x\)
这个图可以看小花的题解,这么简单吗?

ins

正常二叉查找树的ins
有点细节,也就是我们在插入的 \(splay\) 前其实树的 \(siz\) 是错的,那会对吗?不对当且仅当\(push\_up\) 的顺序的一个子序列是从 \(x\) 到根的的节点序列。也很容易理解,就是对于一个递减的序列,前面都是没有用的。那么我们的 \(rotate\) 序列可能是 \(fa->x\),那么我们ro的时候就可以更新 \(fa[fa]\) 的值,所以是对的。

del

我们在二叉查找树上找到这个点,然后旋到根删掉,然后是什么把前驱旋上来?
哦,把前驱旋上来,那么根右子树最左边的节点就是这个点,直接删掉,其实上面我的想法完全假了,直接把右边那个树连到前驱下面就可以了。着。哦,我又想假了,这个想法可能就是常数比较大。
考虑前驱转上来有什么性质,那么就是这个点不会有左子树,那么只要把儿子接上来即可

给定数找排名

插入这个数,然后左子树就是

给定排名找数

普通二叉查找树操作

前驱/后继

普通二叉查找树操作

我们的splay是怎么保证复杂度的呢?
我们每次花链长的时间去进行一个操作的时候我们就 \(splay\) 一下,这样会把特别长的链的长度给缩短一半。

终于调完了

文艺平衡树

一样的建平衡树,然后区间反转操作。
我们首先怎么剖出 \(l-r\) 这颗树呢?我们先把 \(l-1\) 旋上去,然后再把 \(r+1\) 旋上去,这样我们首先得有两个哨兵节点,我们把 \(r+1\) 旋上去的时候当我们 \(splay\)\(fa[r+1]=l-1\) 的时候 \(splay\) 就结束了,然后我们给这个东西打一个标记即可。然后我们还要找平衡树里面的第 \(k\) 大,这个也简单

这个 \(splay\) 是从下往上做的,那这个 \(push\_down\) 是对的吗?\(push\_down\) 不对当且仅当树的形态不对。
原因
因为在 \(splay\) 之前,已经执行了 \(find\) 操作(其中有 \(push\_down\)),所涉及到的所有懒标记都已经下传过了。所以也就不需要在 \(rotate\) 中再下传了。
然后就做完了

posted @ 2025-07-09 10:15  wuhupai  阅读(10)  评论(0)    收藏  举报