Perface
-
先前学了一发Splay,觉得并不是很难,这里做一个小总结.
-
鉴于理解splay的文章很多,但真正有关模板的好文章很少,本文不会进行深入讲解,仅仅是把其对应的一些操作总结一下.
线段树 VS Splay
-
我们都知道对于一般序列上的问题,要维护的,一般都可能想到线段树.
-
但实际上,线段树能做的东西只是Splay能做的东西中的一个子集.
-
Splay vs 线段树
-
从代码难易程度上,显然线段树占优.
-
从一般时间上,线段树能做的题,速度大多比Splay快,ZKW优势明显.
-
但从用途上,Splay能做的东西就要多很多
-
例如在某某位置插入一个数,删除一个数,旋转一堆数,再询问答案.
-
这是线段树所做不到的.
-
-
splay基础操作
-
第一个操作: R o t a t e ( x ) Rotate(x) Rotate(x)
- 定义为把 x x x旋转到 x x x父亲位置,并保证Splay的中序.
-
注意: s o n ( x ) son(x) son(x)的简洁用法,以及两个 i f if if和更新父子关系的先后顺序.
-
其中 s o n ( x ) son(x) son(x)可以如下使用:
-
第二个操作(Maintain): S p l a y ( x , y ) Splay(x,y) Splay(x,y)
- 定义为把 x x x通过 R o t a t e Rotate Rotate操作旋转到 y y y的儿子中.
其中,这个操作还有如下简洁地写法:
-
第三个操作(区间修改操作): r e m o v e ( x , y ) remove(x,y) remove(x,y)
- 定义为把从 x − > y x->y x−>y这一条路径上所有的标记释放.
-
其中 c l e a r clear clear操作如下实现:
-
这里的情况显然是给一段数加个值.
-
l a z y lazy lazy操作则可以根据所需情况实现:
-
第四个操作(求答案): u p d a t e ( x ) update(x) update(x)
-
定义为处理 x x x点的答案.
-
.
-
-
注意到,仅用以上四个操作,我们就可以完成基本的 s p l a y splay splay操作,我们几乎可以切掉所有线段树能切的题了.
-
但接下来,我们还是得讲一些比较nb的操作:
-
需要支持的操作有:
-
插入x数
-
删除x数(若有多个相同的数,因只删除一个)
-
查询x数的排名(若有多个相同的数,因输出最小的排名)
-
查询排名为x的数
-
求x的前驱(前驱定义为小于x,且最大的数)
-
求x的后继(后继定义为大于x,且最小的数)
-
Splay进阶操作
-
操作一: i n s e r t ( x ) insert(x) insert(x)
- 定义为在一颗 s p l a y splay splay中插入权值为 x x x的点.
-
我们看一下这个操作有什么需要注意的:
-
①我们需要判断 r o o t root root是否为 0 0 0,如果是,代表当前树为空,进行特殊操作.
-
②然后我们从 r o o t root root开始走,每次需要判断当前的 x x x是否以前出现过,出现过则加计数器
-
③否则,我们就可以新开节点.
-
④这里需要注意的是update操作,有人可能会不解,这里没有进行 r e m o v e remove remove操作,如何保证 i n s e r t insert insert后所有的节点都被更新?
- 对于这一点,你只需要关注: s p l a y ( n o w , 0 ) splay(now,0) splay(now,0)这一条语句即可.
-
-
操作二: f i n d ( x ) find(x) find(x)
- 定义为找到权值为 x x x点的排名(若有多个相同的,因输出最小的排名)
-
这个操作需要注意的不多:
-
但也需要时刻关注题目,这里的rank+1很关键
-
以及代码是如何处理节点的去向,最后,最关键的是:
-
一定要记得 s p l a y ( n o w , 0 ) splay(now,0) splay(now,0),否则均摊会失效.
-
-
操作三: p r e ( ) pre() pre()
- 定义为求 r o o t root root的前驱,当然我们也可以设为 x x x的前驱.
-
操作四: n e x t ( ) next() next()
- 定义为求 r o o t root root的后继.
-
操作五: u p d a t e ( x ) update(x) update(x)
-
这里的定义为维护子树大小.
-
我们也可以理解为我们需要维护的一些东西.
-
-
操作六: d e l e t e ( x ) delete(x) delete(x)
- 定义为把权值为 x x x的点给删掉一个.
-
这是一个比较麻烦的操作:
-
第一,我们需要注意对于左子树空,右子树空,当前 x x x有多个节点时的特殊处理.
-
很重要的一步是, f i n d ( x ) find(x) find(x),目的很显然了,就是把权值为 x x x的点移到树根.
-
注意,这里万万不可直接 s p l a y ( x ) splay(x) splay(x),因为权值和编号是不一样的概念.
-
splay的形态是按权值的,但具体记录父亲儿子是按编号的.
-
然后如果左儿子右儿子都存在时,我们可以 p r e ( ) pre() pre()找到刚好比 x x x小的一个点作为根,然后再修改父子关系.
-
最后注意一定要 c l e a r ( r o o t ) clear(root) clear(root)以及 u p d a t e ( r o o t ) update(root) update(root)
-
-
操作七: f i m ( x ) fim(x) fim(x)
- 定义为寻找排名为 x x x的权值.
-
这应该就很好理解了吧,注意rank+size[tr[nw][0]]中的rank一词.
-
拥有以上七个操作,这道题就可以迎刃而解,但是我们还有一些细节需要注意:
-
如果我们要求 x x x的前驱,我们可以先插入这个节点,然后再找,最后记得删掉,求后继一样.
-
这样很方便.
-
总结
-
从此文我们可以看到splay的强大之处,以及C++的简洁之处.
-
我们需要注意的是核心 s p l a y ( x , y ) splay(x,y) splay(x,y)
-
不管对于什么样的操作,insert也好,delete也好,都一定要把我们进行操作修改的点 s p l a y splay splay一下.
-
这是核心,一定时刻牢记.
-
一句话:splay是越多越好.
-
其次,我们还需要注意在 r o t a t e rotate rotate的时候一定要先 u p d a t e ( y ) update(y) update(y)再 u p d a t e ( x ) update(x) update(x),原因很显然.
- 然而如果不这么做交到Bzoj上还是能A掉…