更快的带交集无旋 Treap 合并
维护可重集的合并
一般手法
- 权值线段树: 均摊时间 \(O(n\log n)\),还可以支持分裂,但空间开销巨大
- 平衡树启发式合并:空间 \(O(n)\) 但总时间高达两个 \(\log\)。
非旋 Treap 合并
这个科技的时间复杂度为均摊 \(O(n\log n)\),但我不会证(带分裂应该是假的)。在这里感谢 Mr_Spade 给我介绍这个(并不算非常复杂的)科技。
考虑现在有两棵 Treap,根为 \(x, y\)。我们先比较两个结点的随机值,钦定随机值小的作为当前的根。这里假定为 \(x\)。然后我们需要搞出 \(x\) 的左右子树,分别为两棵 Treap 并集(除去 \(x\))中 \(< x\) 和 \(>x\) 的权值的结点构成的 Treap(\(=x\) 的可以特殊处理)。
对于 \(x\),显然它的左右子树(\(l_1, r_1\))就满足上面那个要求;而对于 \(y\) 我们则可以直接按 \(x\) 的权值 split,得到 \(l_2, r_2\) 两棵树。
最后我们发现这是一个可以递归处理的问题,因为我们再对 \(l_1, l_2\)、\(r_1,r_2\) 分别做这样的合并即可,两次合并的结果就可以作为 \(x\) 的两个子树。
参考代码实现:
int join(int x, int y) {
if (!x || !y) return x | y; // 有一个空间的即可返回
if (t[x].pty > t[y].pty) swap(x, y); // 取随机权值小的作为根
int L1 = t[x].ch[0], R1 = t[x].ch[1], L2 = 0, R2 = 0, equ = 0; // x 直接是左右子树
split(y, t[x].val, L2, R2), split(L2, t[x].val - 1, L2, equ); // y 按权值 split
if (equ) t[x].cnt += t[equ].siz, t[x].siz += t[equ].siz; // 相等特殊处理
t[x].ch[0] = join(L1, L2), t[x].ch[1] = join(R1, R2); // 递归合并
return pushup(x), x; // 更新信息
}
实际上这样常数并不大,在空间优于线段树的同时也不会比线段树慢。
效率测试
题目:Luogu P5494 【模板】线段树分裂。由于此题带分裂所以复杂度有点问题,但可以测试效率。
先放一个 评测结果(I/O 优化,O2)。这个排在时间最优解第一页(2020-10-23,现在好像被一堆卡常巨怪挤出去了QAQ),去掉 I/O 优化的话空间用的也很少。
本文来自博客园,作者:-Wallace-,转载请注明原文链接:https://www.cnblogs.com/-Wallace-/p/merge-treaps.html