treap学习笔记

时间:2023-03-09 01:40:19
treap学习笔记

treap是个很神奇的数据结构。

给你一个问题,你可以解决它吗?

这个问题需要treap这个数据结构。

众所周知,二叉查找树的查找效率低的原因是不平衡,而我们又不希望用各种奇奇怪怪的旋转来使它平衡,那么该怎么办呢?这时候,FHQ跳出来说了一句:我的treap,不需要旋转!

FHQ Treap的基本操作

首先要理解节点的修正值fix。这个值是在初始化的时候随机出来的,这个treap中所有的节点的fix要满足堆序

split

第一个操作是把一个treap分成两个treap,其中第一个treap的元素个数/数值最大值有要求,并且第一个treap中的所有元素要小于第二个treap中的所有元素

这个很好满足堆序,因为有这样一个性质:一个treap的子树一定也是个treap。所以我们递归一下,判断这个大treap的左子树是否满足条件,是否可以把左子树全部归给第一个treap就可以了。

merge

第二个操作是把两个treap合并成一个treap,其中第一个treap的所有元素小于第二个treap中的所有元素

二叉查找树的性质很好满足:第一个treap肯定是放在第二个treap的左子树或者第二个treap放在第一个treap的右子树。堆序也很好满足:直接判断两个根的fix的大小情况,看是把第一个treap放在第二个treap的左子树还是第二个treap放在第一个treap的右子树即可。


然后,根据这两个操作,我们可以轻易地做到所有的询问:

  1. 插入x:把treap按照小于等于x和大于x分成两个treap,把x作为单独一个treap,几个按顺序合并即可。
  2. 删除x:把treap按照小于x,等于x和大于x分成三个treap,把中间的那个扔掉,另外两个合并即可。
  3. 查询x的排名:把treap按照小于x和大于等于x分成两个treap,第一个treap的大小+1就是x的排名。
  4. 查询排名为x的数:直接和正常二叉查找树一样递归寻找大小大于等于k的第一个子treap就可以了
  5. 求x的前驱:把treap按照小于x和大于等于x分成两个treap,第一个treap的最后一个数就是x的前驱。
  6. 求x的后继:把treap按照小于等于x和大于x分成两个treap,第二个treap的第一个数就是x的后继。

代码

FHQ Treap的高级操作

前面一道题直接套模板就很好解决,但是如果是这个问题呢?

这里就需要一些类似于线段树的懒标记的东西。对于每一个节点,我们除了之前存的子树大小,节点的值,节点的修正值外,还需要存下列东西:

  • 该子树统一被赋的值
  • 该子树是否已经被反转了
  • 该子树的所有数字的和
  • 该子树的最大前缀和、后缀和(可以不选)、子序列和

其中,第四项是专门处理6号询问的,我们可以用一种分治的思想:对于一个子treap内的最大子序列和,我们可以分成三种情况考虑:

  1. 这个最大和子序列全部位于这个treap的左子树内
  2. 这个最大和子序列全部位于这个treap的右子树内
  3. 这个最大和子序列跨左右子树,同时也包含这个节点上的数

前两种情况可以递归处理,第三种情况可以分成左子树的一个后缀+当前节点的值+右子树的一个前缀。

而最大前缀、后缀和又如何记录呢?以前缀为例,我们可以分成两种情况考虑:

  1. 这个最大和前缀完全包含在左子树内
  2. 这个最大和前缀跨整个左子树,当前节点和右子树的一个前缀

第一种情况可以递归处理,第二种情况也不难,就是左子树的和+当前节点值+右子树最大前缀

然后下推操作应该怎么实现呢?

不难发现顺序是这样的:

  1. 反转区间,即交换左右子树
  2. 修改权值,即把当前子treap统一赋的值更新
  3. 修改和,就根据新的赋值乘一下这个子树的大小

然后还要一个上拉操作,就是更新最大前缀、后缀和以及最大子序列和的操作,按照上面讲的做一下就好了

容易写错的地方

  1. 下推操作时注意是把两个儿子的子树和、前缀后缀最大和更新,而在刚打上标记的时候就应该把自己更新
  2. 反转区间的时候需要把最大前缀/后缀和交换一下

代码