【Coel.做題筆記】【開學-重啟】無旋轉二叉搜尋堆(FHQ-Treap)
題前碎語
回來啦!
雖然其實在寒假做了很多很多題<-做了題你也不寫部落格
新學期到了一個完全不認識的班,只能繼續努力啦!
距離\(CSP\)還有不到200天,加油吧!
題目簡介
P3369 【模板】普通平衡樹
洛谷傳送門
題目描述
您需要寫一種資料結構(也就是普通平衡樹),來維護一些數,其中需要提供以下操作:
- 插入 \(x\) 數
- 刪除 \(x\) 數(若有多個相同的數,因只刪除一個)
- 查詢 \(x\) 數的排名(排名定義為比當前數小的數的個數 \(+1\) )
- 查詢排名為 \(x\) 的數
- 求 \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)
- 求 \(x\) 的後繼(後繼定義為大於 \(x\)
輸入格式
第一行為 \(n\),表示操作的個數,下面 \(n\) 行每行有兩個數 \(\text{opt}\) 和 \(x\),\(\text{opt}\) 表示操作的序號( \(1 \leq \text{opt} \leq 6\) )。
輸出格式
對於操作 \(3,4,5,6\) 每行輸出一個數,表示對應答案。
正文
平衡樹有很多寫法,例如旋轉二叉搜尋堆(\(Treap\)),伸展樹(\(Splay\)),紅黑樹(\(Red\) \(Black\) \(Tree\))等等。在某位學長大佬的介紹下,我選擇了無旋轉二叉搜尋堆(\(FHQ-Treap\),也就是範浩強\(Treap\)
基本思路
平衡樹是二叉搜尋樹的變種。
一般的二叉搜尋樹也支援查前驅後繼、查排名等基本操作。一般情況下,二叉搜尋樹的查詢效率能保持在\(O(logn)\),但如果故意構造插入節點的順序,可能使得二叉搜尋樹退化成一條鏈,效率變成\(O(n)\)。
因此,平衡樹通過各種各樣的方式保證二叉搜尋樹不被退化,從而把效率優化回到\(O(logn)\)。
\(FHQ-Treap\)如何保持效率呢?給每個節點額外加上一個隨機權值,並且把二叉搜尋樹維護到具有堆的性質。
一般的\(Treap\)採用的是旋轉維護堆,而\(FHQ-Treap\)採取的方法是分裂-合併維護堆。
說到這我就想起了珂朵莉樹
操作合集
初始化
為了方便呼叫,我們把整個資料結構封裝到一個結構體裡。
int n, root;
struct FHQ_Treap {
int cnt;
int ch[maxn][2], val[maxn], pri[maxn], size[maxn];
//ch[i][0]-左子樹,ch[i][1]-右子樹
}
其中,\(val\)是節點原本的數值,\(pri\)為隨機權值,\(size\)為該節點對應的子樹節點數。
開設一個全域性變數\(root\)表示樹根的編號,\(cnt\)表示總節點數。
pushup維護size
左子樹\(+\)右子樹\(+1\)(節點自己)
inline void pushup(int x) {
size[x] = size[ch[x][0]] + size[ch[x][1]] + 1;
}
New_node新建節點
\(cnt+1\),維護對應的左右子樹、權值和數值。
void New_node(int &id, int v) {
size[++cnt] = 1;
val[cnt] = v;
pri[cnt] = rand();
ch[cnt][0] = ch[cnt][1] = 0;
id = cnt;
}
split分裂操作
先扔程式碼。
void split(int id, int k, int &x, int &y) {
if (id == 0)
x = y = 0;
else {
if (val[id] <= k) {
x = id;
split(ch[id][1], k, ch[id][1], y);
pushup(x);
}
else {
y = id;
split(ch[id][0], k, x, ch[id][0]);
pushup(y);
}
}
}