简单理解 FHQ-Treap

FHQ Treap 既然是 Treap,那么每个节点就有一个随机分配的权值 \(v\) 和他自己需要维护的权值 \(w\),使得这个 \(v\) 满足大根堆性质,而另一个权值 \(w\) 满足平衡树性质。

FHQ Treap 只需要两个操作:分裂和合并。其中合并需要满足第一棵树的平衡树权值 \(w\) 完全小于第二棵树;这个性质让它可以在 \(O(\log n)\) 时间内快速合并,而不需要像线段树合并那样有一个均摊的复杂度。此外,许多序列上的区间操作都自然地满足这个约束。

如何实现分裂与合并?定义 merge(p,q) 表示将 \(p,q\) 的子树合并,并认为 \(q\) 子树内平衡树权值全部大于 \(p\)。只需要讨论 \(rk_p,rk_q\) 的大小关系即可确定谁是根。分裂的时候,我们讨论根节点应该被分到左边还是右边,并同时维护左右子树的两个“虚拟节点”表示下一个应该插入到哪里。这个分裂并不像线段树那样新建了节点,而是只使用原树中的节点。

写得好:https://www.luogu.com.cn/blog/85514/fhq-treap-xue-xi-bi-ji

自然地,fhq treap 可以用于维护序列。例如要维护区间和,单点插入,那么只需要在每个节点处维护子树和,插入则只需要将前缀后缀分裂出来,然后再合并即可。

P3850

板题。

P4146

本题需要维护区间和,区间加,区间翻转!

我们的懒标记需要满足,访问到这个节点的时候,这个节点的值是正确的。这里引一段话:

如同线段树一样,对于区间操作,是不能一个个进行修改操作的,我们需要懒标记来记录一个区间操作的暂存信息,只有当我们访问时才下放标记,那么在 FHQ-Treap 中什么时候下放标记呢?

答案是当树的结构发生改变的时候,换言之,当我们进行分裂或合并操作时需要改变某一个点的左右儿子信息时之前,应该下放标记,而非之后,因为懒标记是需要下传给儿子节点的,但更改左右儿子信息之后若懒标记还未下放,则懒标记就丢失了下放的对象,就会导致程序的错误。所以在split的时候在赋虚拟节点以信息之前,下放当前 \(now\) 节点的懒标记,在 merge 的时候若 \(u\) 为根,则事先下放 \(u\) 的懒标记,否则下放 \(v\) 的懒标记。

注意在 pushup/pushdown 的时候都需要特判没有左右儿子的情况。平衡树不一定满足每个节点要么没有儿子,要么有两个儿子的性质。

P5217

每个点维护 \(26\)bool 表示是否存在这种字母,实现的时候可以压成一个 int

还需要查询初始文本中某个字母目前的位置;考虑维护两棵 FHQ Treap,一棵维护原序列,另一棵维护当前序列,在当前序列的 fhq treap 上记录每个节点内原序列中结点的个数即可。

可持久化

考虑 split。发现不管怎样当前节点都会被分给 \(x,y\) 中的一个。

因此可以直接把当前节点复制一份;如果需要 pushdown,那么在 pushdown 的时候临时新建节点即可。

考虑 mergemerge 的时候,以谁为根就把谁复制一份,再进行 pushup,即可。

https://www.luogu.com.cn/blog/85514/fhq-treap-xue-xi-bi-ji 中提到如果先 split 之后立即 merge 就不需要新建节点,这是因为 split 只会影响到左区间树的最右链,和右区间树的最左链;merge 影响的节点恰好也是这些,因此不需要新建节点。

如果某些题目中不需要用到历史版本,但因为某些原因需要可持久化,那么我们可以进行定期重构。

posted @ 2023-03-31 18:04  云浅知处  阅读(184)  评论(0编辑  收藏  举报