1. 程式人生 > 其它 >【Coel.做題筆記】【開學-重啟】無旋轉二叉搜尋堆(FHQ-Treap)

【Coel.做題筆記】【開學-重啟】無旋轉二叉搜尋堆(FHQ-Treap)

rua!

題前碎語

回來啦!
雖然其實在寒假做了很多很多題<-做了題你也不寫部落格
新學期到了一個完全不認識的班,只能繼續努力啦!
距離\(CSP\)還有不到200天,加油吧!

題目簡介

P3369 【模板】普通平衡樹
洛谷傳送門

題目描述

您需要寫一種資料結構(也就是普通平衡樹),來維護一些數,其中需要提供以下操作:

  1. 插入 \(x\)
  2. 刪除 \(x\) 數(若有多個相同的數,因只刪除一個)
  3. 查詢 \(x\) 數的排名(排名定義為比當前數小的數的個數 \(+1\) )
  4. 查詢排名為 \(x\) 的數
  5. \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)
  6. \(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);
		}
	}
}