真正好打的平衡树
Splay
首先是Splay的定义,上代码:
1 | struct Splay{ |
Splay的基本操作是旋转,在保证中序遍历不变的情况下,通过旋转将树的结构改变,如下图中 $x$ 、 $y$ 、 $z$ 是三个节点,而 $A$ 、 $B$ 、 $C$ 是三棵子树,在将 $x-y$ 左右旋转后,树的结构发生变化:
如上图中,将 $x-y$ 右旋,那么 $z$ 就完全不动,而旋转后我们发现 $A$ 、 $B$ 、 $y$ 都成了 $x$ 的子节点,这显然不满足BST性质。为了满足BST,我们比较三个子节点(子树),发现由BST性质,有 $A<x<B<y<C$ ,明显,为了BST性质,只能让 $A$ 继续做 $x$ 的左子树,将 $B$ 、 $y$ 、 $C$ 构造成 $x$ 的右子树。
具体实现如下:
1 | //计算x的儿子数量 |
而在Splay中我们插入、修改、删除任何一个节点的方式就是将该节点转至根节点,再进行操作。
为将节点 $x$ 转至根节点,我们定义函数 $Splay(x,k)$ 表示将节点 $x$ 转至节点 $k$ 下方(即 $x$ 是 $k$ 的子节点),通过 $Splay(x,0)$ ,我们可以将 $x$ 转至根节点。
而为了实现该函数,我们发现有如下两类情况(以下我们设 $y$ 是 $x$ 的父节点, $z$ 是 $y$ 的父节点):
$x$ 、 $y$ 、 $z$ 在同一直线,如图:
$x$ 、 $y$ 、 $z$ 不在同一直线,如图:
而对于这两种情况,我们也有不同的策略:
先转一次 $y$ ,再转一次 $x$ (具体方向取决于是左儿子还是右儿子)如图:
连转两次 $x$ (具体方向取决于是左儿子还是右儿子)如图:
代码实现如下:
1 | void splay(int x,int k){ |
在实现 $Splay()$ 后,我们就可以完成操作。
插入
将一个数 $x$ 插入到Splay中:
1 | void insert(int x){ |
查找第k个数
找到第 $k$ 大的数:
1 | int get_k(int k){ |
查找x的位置
该函数可以把数 $x$ 转到根节点,完成后x的排名就是
$tr[tr[root].ch[0]].size+1 \sim tr[tr[root].ch[0]].size+tr[root].cnt$
1 | void find(int x){ |
查找前驱/后继
该函数可以找到数 $x$ 的前驱( $f=0$ )或后继( $f=1$ )(大小关系严格):
1 | int near(int x,int f){ |
删除数x
在Splay中删除 $x$ :
1 | int remove(int x){ |
中序输出
1 | void midout(int u){ |
下传懒标记
以下用区间翻转举例:
1 | void push_down(int x){ |
代码
1 |
|