[总结] 无旋treap

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[总结] 无旋treap相关的知识,希望对你有一定的参考价值。

- 导语 -

顾名思义就是没有旋转操作的treap.
还是很好打的.
毕竟旋转操作旋转上天.

- 学习 -

两个核心操作: splitmerge

split是将一棵树分成两棵树的操作.
注意这里的要求是对于确定的树,将其前k个点分成新树, 剩下的点变成另一颗新树,因此可能出现多个切割的地方.
对于一个节点来说,我们必然只会处理它的一颗子树,因此用递归去找处理的子树就行了.
返回时对于每一个点更新一下它被处理的那颗子树.
函数的返回值是两颗子树的根.
具体看代码吧

define pii pair<int,int>
define mp make_pair

pii split(int rt, int k) { //对于根为rt的树,将它前k个点裂成一棵树A,剩下的点成为树B

  if (!rt) return mp(0, 0);
  
  pii tmp;
  pushdown(rt);
  
  if (k > S[C[rt][0]]) { //处理右子树
    tmp = split(C[rt][1], k - S[C[rt][0]] - 1);
    //tmp表示右子树分裂出来的两棵树(A,B),
    //其中左边(A)的是当前rt的新的右子树
    C[rt][1] = tmp.first; pushup(rt); tmp.first = rt;
    //更新rt, 然后将rt作为一个新的左子树,
    //原右子树分裂出来的右边的新树(B)作为新的右子树,
    //将这颗新树返回
    
  }
  else { //处理左子树
    tmp = split(C[rt][0], k);
    C[rt][0] = tmp.second; pushup(rt); tmp.second = rt;
  }
  
  return tmp;

}

merge即是将两颗树合并的操作, 注意这里合并的树(A,B)要求max_value(A) < min_value(B),这样把AB一左一右相接(即保证B的每一个节点都在A的右边)便保证了权值的有序,我们就只要维护堆的性质了.(显然split分出来的两颗树就满足这样的性质)

int merge(int ra, int rb) { //返回新树的根

  if (!ra) return rb;
  if (!rb) return ra; //有一颗空树,直接合并
  
  pushdown(ra);
  pushdown(rb);

  if (KEY[ra] < KEY[rb]) { //ra的key值较小,维护小根堆的话要放在上面
    C[ra][1] = merge(C[ra][1], rb);
    //默认rb是接在右边的树,因此rb必然会接进ra的右子树中
    pushup(ra); return ra;
    //不要忘记更新
  }
  else {
    C[rb][0] = merge(ra, C[rb][0]);
    pushup(rb); return rb;
  }

}

- 单点操作 -

这里是题目.
单点操作基本都能靠merge+split完成.
比如这题只需加上splay中一样的getkth(找到第k个数), findkth(找到数A的位置),
那么:
insert=getkth(findkth+getkth)+split+merge
delete=getkth(findkth+getkth)+split+merge
单点插入删除也可用(merge)(split)完成.

- 区间操作 -

这里是题目.
其实和单点操作没什么区别...
区间的插入删除也是使用(merge)(split)完成.
删除好说,但是注意插入时需要我们先建好一颗子树再merge.
于是又有了一个build函数.
我们可以一个一个把点插到新树中去(一开始有一颗空树).
那么每次插入的点必然在树的最右端.

然后开始维护小根堆的性质.
考虑root -> right son -> right son ... 这样一条链, 我们先把新点接在这条链最下面,
然后找到其中深度最小的一个key值大于大于点的节点,把以它为根的子树当做新点
的左子树, 然后用新点代替它原来的位置就可以了.(相当于把新点沿着链一直向上旋)
但是需要注意排布在这条链上的树是没有维护(pushup/update)过的, 因此每次
寻找到要被移到新点下面的点都需要一次pushup, 最后再给仍在链上的点来一发pushup.
因为每次加入的点都在链上, 可以证明每个点都会(在它的所有子树之后)经过一次pushup

还有一个需要注意的点是splay中的虚点.
无旋treap并不需要虚点,但是在pushup的时候可能考虑到空子树的情况,为避免空子树的影响
需要一个初始化.

以上是关于[总结] 无旋treap的主要内容,如果未能解决你的问题,请参考以下文章

算法学习Fhq-Treap(无旋Treap)

[代码] bzoj 1500 维修数列(无旋treap)

[代码] bzoj 3224 普通平衡树(无旋treap)

无旋Treap

无旋Treap

模板 - 数据结构 - 无旋Treap / FHQ Treap