Splay操作集合

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的操作:

  • 以:http://www.lydsy.com/JudgeOnline/problem.php?id=3224此题为例.

  • 需要支持的操作有:

    • 插入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掉…
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值