若“值”和“位置”还容易混,先完成 课前零食:把一段话翻过来。它用同一段文字分别预演隐式下标、双哨兵和翻转 Lazy;本节换成 FHQ 的 split/merge 接口。
把中序位置而非键作为顺序,使用子树大小按前 k 项分裂;维护翻转 Lazy,并完成区间反转、剪切和拼接。
前置:上一节 FHQ Treap、Lazy 标记。
key 满足 BST 顺序?split(root,k) 返回的两树各含多少序列元素?数组插删中段会搬移 $O(n)$ 个元素。隐式 Treap 的中序遍历代表序列,split 按左子树大小决定一个节点属于前 k 项还是后缀。任意区间 $[l,r]$ 可两次 split 暴露成独立根:
prefix_and_mid, suffix = split(root, r)
prefix, mid = split(prefix_and_mid, l-1)
对 mid 打标记,再按原顺序 merge 回去。
区间翻转不会改变区间外元素,也不会改变区间元素集合;独立子树的中序翻转恰由交换每个节点左右儿子实现。Lazy 在访问前下传,保证任何时刻从逻辑上读取的中序序列等于所有待执行翻转后的真实序列。每次两次 split、两次 merge,期望 $O(\log n)$。
left_mid, right = split(root, r)
left, mid = split(left_mid, l-1)
apply_reverse(mid)
root = merge(merge(left, mid), right)
两次 split 按原序列排名得到三段,按题意顺序重新 merge。split 保持每段中序相对顺序,merge 在所有左段元素应位于右段之前的前提下保持各自内部顺序,所以新中序序列精确等于剪贴结果。每次期望 $O(\log n)$。
left_cut, suffix = split(root, b)
prefix, cut = split(left_cut, a-1)
root = merge(merge(prefix, suffix), cut)
节点再维护 sum/min,区间根暴露后即可查询;仿射、加法等 Lazy 需按普通线段树原则定义作用和复合。可持久化隐式 Treap 要路径复制,破坏式 merge 不能共享旧根。
与 std::string 的 reverse/erase/insert 对拍。每次操作后同时检查中序长度与原长度相等,优先捕捉丢根或重复合并。