啓發式合併
- 剛聽到這個東西的時候,我是相當蒙圈的。特別是“啓發式”這三個字莫名的裝逼,因此之前一直沒有學。
- 實際上,這個東西就是一個SB貪心。
- 以堆爲例,若我們要合併兩個堆a、b,我們有一種極其簡單的做法:那就是比較一下它們的大小,將小的堆的每個元素依次插入到大的堆中。不妨設 ,則時間複雜度即爲: 。
- 這個東西看似很慢,但當點數較小的時候,我們可以證明複雜度是可被接受的。
- 比如我們要合併n個堆,這n個堆共有m個點。設這n個堆 。
- 首先,我們合併 和 ,變成一個新的堆 。
- 然後,我們合併 和 ,變成一個新的堆 。
- ……
- 以此類推,我們最終可以合併出一個堆 。
- 合併堆a、b時,記1次操作爲將a中的一個元素插入b(或將b中的一個元素插入a)。
- 可以發現,第1次合併操作數 ,第2次合併操作數 ……第i次合併操作數 。
- 因此,總操作數 。而每次操作又是 的複雜度。因此:
- 時間複雜度: 。
推廣
- 啓發式合併也可以用到set、splay、treap等平衡樹上去。
- 若我們要合併兩棵平衡樹a、b,也是先比較大小,將小的平衡樹的每個元素依次插入大的平衡樹。囿於插入的時間也是 ,因此總複雜度還是 。
- 注意:這裏的合併並非treap的merge。merge(a,b)是強行讓a所有元素的鍵值(要滿足二叉排序樹的性質的那個值)均小於b所有元素的鍵值,所以可以 做到;而這裏要合併的兩棵平衡樹a、b的鍵值可能是交錯不齊的。
線段樹合併
- OI中常常遇到一些題目,要將若干物件不斷合併,維護信息。
- 如果合併的順序不對,堆/平衡樹的啓發式合併會很慢。比如當你分治+啓發式合併的時候,時間複雜度就變成 了。
- 這個時候,就需要線段樹合併。
- 對於這個,相信大家都想得出下面這種合併步驟:
- 爲了方便確定一棵樹是否爲空,我們動態開點。
- 比如,我們合併兩棵權值線段樹:
- 顯然,這麼做的複雜度與兩棵樹公共的節點數成正比。
- 但是,假設我們要合併多棵線段樹呢?
- 假設我們要合併n棵線段樹,定義勢能函數 爲它們的節點個數和。
- 每次合併線段樹a、b時,設其公共點數爲c,則合併後的 減少c,而時間複雜度增加c。
- 因此,時間複雜度應≤節點個數和。
- 當線段樹中總共有m個元素時(比如n棵權值線段樹,只存有m個數),每個元素都可以動態開闢至多 個節點。因此,此時的時間複雜度應爲 。
- 注意:此時的時間複雜度並不受合併順序的限制。換句話說,不論你按什麼順序合併,只要你是合併n棵只有m個元素的線段樹,時間複雜度就是 。
例題
【BZOJ 2212】【Poi2011】 Tree Rotations
【JZOJ5800】【洛谷P4416】 [COCI2017-2018#1] 被單