引入
题目来源:文艺平衡树 - 洛谷P3391
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1 5 4 3 2 1 ,翻转区间是 [ 2 , 4 ] [ 2 , 4 ] 的话,结果是 5 2 3 4 1 5 2 3 4 1 。 对于 100 % 100 % 的数据,1 ≤ n 1 ≤ n (初始区间长度)m m (翻转次数)≤ 1 e 5 ≤ 1 e 5 。
解决方法
想用平衡树解决区间问题,那么首先考虑如何建树,要求建树建出来就是初始区间。
事实上,只需要把区间的下标插入 treap 中,然后中序遍历即可得到这个区间。
在朴素的 BST 中,按顺序插入结点建出来的是一条链,但在 treap 中,按顺序插入结点后,merge 操作会根据每个节点的 pri 调整树结构,问:如何保证中序遍历一定能正确输出 ?\
OI Wiki如是解释:
可以参考 笛卡尔树的单调栈建树方法 来理解这个问题。
设新插入的节点为 u u 。
首先,因为是递增地插入节点,每一个新插入的节点肯定会被连接到 treap 的右链(即从根结点一直往右子树走,经过的结点形成的链)上。
从根节点开始,右链上的节点的 pri 是递增的(小根堆)。那我们可以找到右链上第一个 pri 大于 u u 的节点,我们叫这个节点 v v ,并把这个节点换成 u u 。
因为 u u 一定大于这个树上其他的全部节点,我们需要把 v v 以及它的子树作为 u u 的左子树。并且此时 u u 没有右子树。
可以发现,中序遍历时 u u 一定是最后一个被遍历到的(因为 u u 是右链中的最后一个,而中序遍历中,右子树是最后被遍历到的)。
博主本人的理解,对照这个图,如果你学过旋转平衡树,你会发现,他事实上可以理解为干了这么一件事:
把 5 , 2 5 , 2 插入到 4 , 5 4 , 5 的右儿子;
旋转上升
不过是 FHQ Treap 本身并不支持旋转操作,这里就换了一种方式,同样达到了旋转的效果。
那么接下来考虑如何实现区间翻转。
假设你想翻转 [ l , r ] [ l , r ] ,你的想法就很自然的是先把这个区间给剥离出来再操作。
也就是将树分裂成 [ 1 , l − 1 ] , [ l , r ] , [ r + 1 , n ] [ 1 , l − 1 ] , [ l , r ] , [ r + 1 , n ] 三个区间,再对中间的 [ l , r ] [ l , r ] 进行翻转。
通过画图,可以发现翻转的具体操作是把区间内的子树的每一个左,右子节点交换位置。
显然的,这个操作的复杂度最坏 O ( n ) 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__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具