以HDU4453为例,整理了一些Splay的题型
/*
【算法介绍】
Splay叫做伸展树,是一种二叉搜索树,也可以说是一种平衡树结构。
其可以维护节点的左右次序值,也就是说,我们在Splay上做中序遍历的次序输出节点,得到的便是所有节点的左右次序。
【数据结构】
int ch[N][2], fa[N]; //节点的链接关系
int num[N], sz[N]; //节点个数与子树大小
1,anc定义为该平衡树实际上的根,然而其中并不存放任何信息,初始有const anc = 0
2,#define rt ch[anc][0] 为该平衡树逻辑上的根
3,#define keynode ch[ch[rt][1]][0] 为逻辑根的右儿子的左儿子,可以方便我们做一系列操作
【基本操作】
1,int newnode(int val) 新建节点,给节点赋予初值val
2,int D(int x) 返回x是其父节点的哪个儿子
3,void setc(int x, int y, int d) 设置x的d号儿子为y
4,void rotate(int x) 旋转使得x与其父节点交换位置,然而其左右关系保持不变
5,void splay(int x) 把节点x旋转到anc的左儿子位置,即rt位置。该操作的旋转特性使得总复杂度维持在log级别
有了这5个操作,我们可以在不改变中序遍历的条件下该边子孙关系了。
【查找插入操作】
然而,splay的节点除了父子关系外,还具有权值v[]。
如果,v[]只是其先后次序的离散化映射,那么,我们便可以在这个二叉搜索树上查询某个值val在树的哪个节点上。
splay是一棵可以快速动态维护的二叉搜索树.
所以我们还可以在树上查找权值所对应的节点:find(val);可以在树上ins(val)
平衡树上的查找操作,除了find()
还有查找最左元素void first(),查找最右元素void last(),以及查找第k小操作void kth()
【高级区间操作】
在这些基础操作之上,我们可以实现一些高级的Splay的区间操作
1,void del(x) 把x节点删除
实现方法:先把x转到rt,再把左子树放到rt,再把右子树放到左子树的右子树位置
2,void segment(l, r) 把[l,r]区间都转到keynode的位置
{ splay(kth(l - 1), anc); splay(kth(r + 1), rt); return keynode; }
实现方法:先把第l-1个节点转到anc的儿子(左儿子)位置,即rt位置。这时[l,n]的所有节点都在rt的右子树上。
然后 再把第r+1个节点转到rt的儿子(右儿子)位置,这时[l,r]的所有节点都在第r+1号节点的左儿子处,即keynode位置。
3,void split(l, r) 把区间[l,r]删除
实现方法:先调用segment(l,r)得到要删除的区间,然后直接删除=w=
4,void inspos(int x, int pos) 把子树x插入到pos位置的右侧
实现方法:先调用segment(pos+1,pos)使得位于rt,pos+1位于rt的右儿子位置(ch[rt][1]位置)
这时ch[rt][1]的左儿子为空,直接把x插入到该位置即可
【修改相关操作】
除了我们需要实现位置的变更以外,有时还需要用splay做区间修改操作。
区间修改操作为了保证复杂度的要求,一定会需要用到延迟标记。
然而,这里的延迟标记与线段树的不同
线段树的延迟标记是位于实际并不存在的虚拟段节点上
而Splay的延迟标记则是切切实实放在某个具体节点上的。
如果一个节点具有延迟性的标记,那么意味着,在其整棵子树上,都应该生效其影响。
于是,我们需要有pushdown()和pushup()的函数
什么时候需要pushdown()?
在我们改变父子关系(即splay操作)的时候,需要对父与子都各自做一次pushdown()
在我们改变做子树遍历查找的时候,因为这里涉及到reverse操作,所以也需要pushdown()
什么时候需要pushup()?
setc()的时候需要做pushup(),而且这个pushup()需要一直延续到根(即rt位置)
【Debug相关操作】
我们还可以通过一定函数实现方便的Debug
有两个问题——
1,左右顺序是我们定的,与val无关
2,左右顺序是由val决定的
*/
#include&l