更快的帶交集無旋 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 優化的話空間用的也很少。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM