Splay操作集合

来源:互联网 发布:广东网络干部学院登录 编辑:程序博客网 时间:2024/06/18 16:43

Perface

  • 先前学了一发Splay,觉得并不是很难,这里做一个小总结.

  • 鉴于理解splay的文章很多,但真正有关模板的好文章很少,本文不会进行深入讲解,仅仅是把其对应的一些操作总结一下.

线段树 VS Splay

  • 我们都知道对于一般序列上的问题,要维护的,一般都可能想到线段树.

  • 但实际上,线段树能做的东西只是Splay能做的东西中的一个子集.

  • Splay vs 线段树

    • 从代码难易程度上,显然线段树占优.

    • 从一般时间上,线段树能做的题,速度大多比Splay快,ZKW优势明显.

    • 但从用途上,Splay能做的东西就要多很多

      • 例如在某某位置插入一个数,删除一个数,旋转一堆数,再询问答案.

      • 这是线段树所做不到的.


splay基础操作

  • 第一个操作:Rotate(x)

    • 定义为把x旋转到x父亲位置,并保证Splay的中序.
  • 这里写图片描述

  • 注意:son(x)的简洁用法,以及两个if和更新父子关系的先后顺序.

  • 其中son(x)可以如下使用:

    • 这里写图片描述

  • 第二个操作(Maintain):Splay(x,y)

    • 定义为把x通过Rotate操作旋转到y的儿子中.
  • 这里写图片描述

其中,这个操作还有如下简洁地写法:

  • 这里写图片描述

  • 第三个操作(区间修改操作):remove(x,y)

    • 定义为把从x>y这一条路径上所有的标记释放.
  • 这里写图片描述

  • 其中clear操作如下实现:

    • 这里写图片描述
  • 这里的情况显然是给一段数加个值.

  • lazy操作则可以根据所需情况实现:

    • 这里写图片描述

  • 第四个操作(求答案):update(x)

    • 定义为处理x点的答案.

    • 这里写图片描述.


  • 注意到,仅用以上四个操作,我们就可以完成基本的splay操作,我们几乎可以切掉所有线段树能切的题了.

  • 但接下来,我们还是得讲一些比较nb的操作:

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

  • 需要支持的操作有:

    • 插入x数

    • 删除x数(若有多个相同的数,因只删除一个)

    • 查询x数的排名(若有多个相同的数,因输出最小的排名)

    • 查询排名为x的数

    • 求x的前驱(前驱定义为小于x,且最大的数)

    • 求x的后继(后继定义为大于x,且最小的数)


Splay进阶操作

  • 操作一:insert(x)

    • 定义为在一颗splay中插入权值为x的点.
  • 这里写图片描述

  • 我们看一下这个操作有什么需要注意的:

    • ①我们需要判断root是否为0,如果是,代表当前树为空,进行特殊操作.

    • ②然后我们从root开始走,每次需要判断当前的x是否以前出现过,出现过则加计数器

    • ③否则,我们就可以新开节点.

    • ④这里需要注意的是update操作,有人可能会不解,这里没有进行remove操作,如何保证insert后所有的节点都被更新?

      • 对于这一点,你只需要关注:splay(now,0)这一条语句即可.

  • 操作二:find(x)

    • 定义为找到权值为x点的排名(若有多个相同的,因输出最小的排名)
  • 这里写图片描述

  • 这个操作需要注意的不多:

    • 但也需要时刻关注题目,这里的rank+1很关键

    • 以及代码是如何处理节点的去向,最后,最关键的是:

    • 一定要记得splay(now,0),否则均摊会失效.


  • 操作三:pre()

    • 定义为求root的前驱,当然我们也可以设为x的前驱.
  • 这里写图片描述

  • 操作四:next()

    • 定义为求root的后继.
  • 这里写图片描述


  • 操作五:update(x)

    • 这里的定义为维护子树大小.

    • 我们也可以理解为我们需要维护的一些东西.

  • 这里写图片描述


  • 操作六:delete(x)

    • 定义为把权值为x的点给删掉一个.
  • 这里写图片描述

  • 这是一个比较麻烦的操作:

    • 第一,我们需要注意对于左子树空,右子树空,当前x有多个节点时的特殊处理.

    • 很重要的一步是,find(x),目的很显然了,就是把权值为x的点移到树根.

    • 注意,这里万万不可直接splay(x),因为权值和编号是不一样的概念.

    • splay的形态是按权值的,但具体记录父亲儿子是按编号的.

    • 然后如果左儿子右儿子都存在时,我们可以pre()找到刚好比x小的一个点作为根,然后再修改父子关系.

    • 最后注意一定要clear(root)以及update(root)


  • 操作七:fim(x)

    • 定义为寻找排名为x的权值.
  • 这里写图片描述

  • 这应该就很好理解了吧,注意rank+size[tr[nw][0]]中的rank一词.


  • 拥有以上七个操作,这道题就可以迎刃而解,但是我们还有一些细节需要注意:

    • 如果我们要求x的前驱,我们可以先插入这个节点,然后再找,最后记得删掉,求后继一样.

    • 这样很方便.


总结

  • 从此文我们可以看到splay的强大之处,以及C++的简洁之处.

  • 我们需要注意的是核心splay(x,y)

  • 不管对于什么样的操作,insert也好,delete也好,都一定要把我们进行操作修改的点splay一下.

  • 这是核心,一定时刻牢记.

  • 一句话:splay是越多越好.

  • 其次,我们还需要注意在rotate的时候一定要先update(y)update(x),原因很显然.

    • 然而如果不这么做交到Bzoj上还是能A掉…