treap——大名鼎鼎的隨機二叉查詢樹,以優異的效能和簡單的實現在oier們中廣泛流傳。
這篇blog介紹一種不需要旋轉操作來維護的treap,即無旋treap,也稱fhq-treap。
它的巧妙之處在於只需要分離和合併兩種基本操作,就能實現任意的平衡樹常用修改操作。
而不需要旋轉的特性也使編寫**時不需要考慮多種情況和複雜的父親兒子關係的更新,同時降低了時間複雜度。
此外,它還可以方便地支援可持久化,實在是功能強大。
接下來系統性地介紹一下無旋treap的原理和實現,最後講解一下應用和例題。
一、treap是啥?
treap是一棵二叉查詢樹,滿足中序遍歷始終是有序的。
treap的每個節點除了儲存資訊外,還要儲存乙個值\(pri\)。
這個\(pri\)儲存的是這個節點的優先順序。
它是幹嘛用的呢?
實際上,treap除了是乙個二叉查詢樹之外,它在另乙個意義下還是乙個堆。
每個節點的子節點,一定要滿足子節點的\(pri\)比父節點的\(pri\)小。\(pri\)越大的點越往上放。
這有什麼意義呢?為什麼要這樣定義?
事實上,\(pri\)值是程式隨機指派的,每個點的\(pri\)值是與這個點的權值無關的,是隨機的。
這樣隨機化就可以保證treap的深度是\(o(log\;n)\),這就是隨機化的力量。
而且可以保證這樣建樹一定不會出現矛盾,現在模擬一下建樹的過程:
先把所有節點按照中序遍歷排好,然後找到其中\(pri\)最大的,把它作為整個treap的根,左邊的節點形成左子樹,右邊的節點形成右子樹。再對左右子樹遞迴處理。
這樣最終就能建好一棵treap。
二、treap的基本操作
講完了treap的定義,來看看treap的兩個基本操作:
分離(split)和合併(merge)。
分離:指的是將一棵treap按照中序遍歷的順序,分割成左右兩半,滿足左右兩半組成的treap的所有值都不變。
合併:指的是將兩棵treap(一般是從原先的treapsplit出來的)合併在一起,按照中序遍歷的順序,並且所有節點的值都不變。
split操作比較簡單,先講講如何實現:當然,split之前要先指定乙個值k,表示split出這個treap的中序遍歷中的前k個數作為第一棵split出的treap。
從這個treap的根開始,看它的左子樹的大小是否大於等於k,如果是,那麼說明右子樹和根都在第二棵中,繼續遞迴到左子樹中。
如果不是,那麼說明左子樹和根都在第一棵treap中,繼續遞迴到右子樹中,而且k要減去左子樹的大小加一。
**:
void split(int rt,int k,int&rt1,int&rt2)if(k<=siz[ls[rt]])
else
}
其中,rt是根,k是分出的第一棵子樹的大小,rt1和rt2用來返回。
combine函式用來維護節點的大小。
接下來看merge操作:
有兩棵treap,假設要把第二棵接到第一棵後面,那麼應該怎麼合併呢?
考慮兩個根節點的\(pri\)值,因為第一棵在第二棵前面,所以要不然rt1(第一棵的根)在rt2(第二棵的根)的左子樹,要不然rt2在rt1的右子樹。
但是因為有了\(pri\)的影響,所以只能rt1和rt2中\(pri\)較大的那個作為根。
如果rt1為根,那麼有rt1的右子樹和rt2合併作為rt1的現在的右子樹。
如果rt2為根,那麼有rt2的左子樹和rt1合併作為rt2的現在的左子樹。
兩種情況都遞迴進子樹中即可。
**如下:
int merge(int rt1,int rt2)
刪除val:先查詢rank(val),然後按照rank把整個treapsplit成三個,刪除需要的點,最後merge剩下兩個。
void delete(int v)
查詢第k個值:把整個treapsplit成三個,輸出需要的值,最後合併起來。
int kth(int k)
前驅:kth(rank(val-1))。
後繼:kth(rank(val)+1)。
還有很多很多操作,供大家腦補。
四、區間操作
無旋treap和旋轉treap的更重要區別是,無旋treap可以很方便地支援區間的操作。
如何支援?你已經看到了,split操作分離出的就是乙個個區間啊!把一整棵treap分離出來一段區間,在上面盡情地修改吧。不過要記得像線段樹寫好區間pushdown哦!
比如區間翻轉,就是左右子樹調換,並且打上標記。區間加減乘除更不用說。
記得在split,merge和rank三個函式內部加上pushdown!
五、實戰應用
有很多平衡樹的題目,都能用treap解決。
luogu p3369 測試你的treap普通操作的熟練程度。
luogu p3391 區間操作的應用。
luoge p3165 區間操作和其他技巧。
演算法學習 FHQ Treap (無旋Treap)
fhq treap和普通的treap都是乙個二叉搜尋堆,其同時滿足二叉樹的性質 左子樹的權值小於等於當前節點權值,右子樹權值大於當前節點權值 和堆的性質 對於小根堆,當前節點的優先順序是堆中最小的 fhq treap與一般的treap的不同之處主要在於 不用旋轉,用split和merge來為維護堆的...
fhq treap(無旋treap) 學習筆記
首先最好要會寫treap 也先了解一下笛卡爾樹是什麼。fhq treap和treap同樣有乙個隨機分配的rnd值,用於平衡,但fhq treap不需要旋轉操作來維持平衡,因為有兩個神奇的操作merge和split 在兩種操作之前,要明確的一點是fhq treap依靠rnd值來維護平衡,把每個點按照小...
Fhq Treap無旋Treap練習
洛谷p3369 模板 普通平衡樹 split採用按權 author revolia submit includeusing namespace std typedef long long ll const int maxn 1e6 5 int l maxn r maxn val maxn size ...