FHQ Treap实现区间操作

引入

题目来源:文艺平衡树 - 洛谷P3391
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1,翻转区间是 [2,4] 的话,结果是 5 2 3 4 1 。 对于 100% 的数据,1n(初始区间长度)m(翻转次数)1e5

解决方法

想用平衡树解决区间问题,那么首先考虑如何建树,要求建树建出来就是初始区间。

事实上,只需要把区间的下标插入 treap 中,然后中序遍历即可得到这个区间。

在朴素的 BST 中,按顺序插入结点建出来的是一条链,但在 treap 中,按顺序插入结点后,merge 操作会根据每个节点的 pri 调整树结构,问:如何保证中序遍历一定能正确输出?\

OI Wiki如是解释:

可以参考 笛卡尔树的单调栈建树方法 来理解这个问题。
设新插入的节点为 u
首先,因为是递增地插入节点,每一个新插入的节点肯定会被连接到 treap 的右链(即从根结点一直往右子树走,经过的结点形成的链)上。
从根节点开始,右链上的节点的 pri 是递增的(小根堆)。那我们可以找到右链上第一个 pri 大于 u 的节点,我们叫这个节点 v,并把这个节点换成 u
因为 u 一定大于这个树上其他的全部节点,我们需要把 v 以及它的子树作为 u 的左子树。并且此时 u 没有右子树。
可以发现,中序遍历时 u 一定是最后一个被遍历到的(因为 u 是右链中的最后一个,而中序遍历中,右子树是最后被遍历到的)。

博主本人的理解,对照这个图,如果你学过旋转平衡树,你会发现,他事实上可以理解为干了这么一件事:

  1. 5,2 插入到 4,5 的右儿子;
  2. 旋转上升

不过是 FHQ Treap 本身并不支持旋转操作,这里就换了一种方式,同样达到了旋转的效果。

那么接下来考虑如何实现区间翻转。

假设你想翻转 [l, r],你的想法就很自然的是先把这个区间给剥离出来再操作。

也就是将树分裂成 [1,l1], [l,r], [r+1,n] 三个区间,再对中间的 [l,r] 进行翻转。

通过画图,可以发现翻转的具体操作是把区间内的子树的每一个左,右子节点交换位置。

显然的,这个操作的复杂度最坏 O(n),不太能够满足我们的需要。我们有什么方法能避免这一点呢?

其实利用线段树的思想。不难发现,翻转再翻转等于没翻转,那么我们在交换时只需要在父节点打上标记,代表这个子树下的每个左右子节点都需要交换就行了。

标记打好了,什么时候下传标记?

回忆在线段树中,我们一般在更新和查询时下传懒标记。这是因为,在更新和查询时,我们想要更新/查询的范围不一定和懒标记代表的范围重合,所以要先下传标记,确保查到和更新后的值是正确的。

FHQ Treap 也一样。具体操作时我们会把 treap 分裂成前文讲到的三个树,然后给中间的树打上懒标记后合并这三棵树。

一句话理解:树的结构发生改变了,你就该下放标记了

为了很快的实现“翻转两次等于没有翻转”,我们想到了使用 异或 来实现。

代码如下:

#include <iostream> using namespace std; const int N = 1e6+10; int n, m, root, l, r, p, cnt; struct { int l, r, size, val, lazy, key; }tree[N]; inline void add(int x) { tree[x].val = x, tree[x].size = 1, tree[x].key = rand(), tree[x].l = tree[x].r = 0; } inline void pushdown(int u) { swap(tree[u].l, tree[u].r), tree[tree[u].l].lazy ^= 1, tree[tree[u].r].lazy ^= 1, tree[u].lazy = 0; } inline void update(int u) { tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + 1; } void split(int u, int x, int &l, int &r){ if(!u) return (l = r = 0), void(); if(tree[u].lazy) pushdown(u); if(tree[tree[u].l].size + 1 <= x) l = u, split(tree[u].r, x - tree[tree[u].l].size - 1, tree[u].r, r); else r = u, split(tree[u].l, x, l, tree[u].l); update(u); } int merge(int l,int r){ if(!l || !r) return l + r; if(tree[l].key < tree[r].key){ if(tree[l].lazy) pushdown(l); return tree[l].r = merge(tree[l].r, r), update(l), l; } else{ if(tree[r].lazy) pushdown(r); return tree[r].l = merge(l, tree[r].l), update(r), r; } } void print(int u){ if(tree[u].lazy) pushdown(u); if(tree[u].l) print(tree[u].l); cout << tree[u].val << ' '; if(tree[u].r) print(tree[u].r); } int main(){ ios::sync_with_stdio(false); cin >> n >> m; for(int i = 1; i <= n; ++ i) add(i), root = merge(root, i); for(int i = 1, x, y; i <= m; ++ i){ cin >> x >> y; split(root, y, l, r), split(l, x - 1, l, p), tree[p].lazy ^= 1, root = merge(merge(l, p), r); } print(root); }

__EOF__

本文作者SDLTF
本文链接https://www.cnblogs.com/sdltf/p/17612690.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   SD!LTF  阅读(163)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示