「學習筆記」重修左偏樹

2023-04-23 09:01:06

左偏樹,是一種可並堆,同時也是一棵二元樹,可以快速地完成合並操作。

dist 的性質

對於一棵二元樹,我們定義左孩子或右孩子為空的節點為外節點,定義外節點的 \(\text{dist}\)\(1\),空節點的 \(\text{dist}\)\(0\),不是外節點也不是空節點的 \(\text{dist}\) 為其到子樹中最近的外節點的距離加一。
一棵根的 \(\text{dist}\)\(x\) 的二元樹至少有 \(2^x - 1\) 個節點。此性質所有二元樹都有,並非左偏樹特有。
\(\text{dist}\) 不是深度,左偏樹的深度沒有保證,一條向左的鏈也是左偏樹。

左偏樹的性質

左偏樹是一棵二元樹,並且是「左偏」的,即每個節點左兒子的 \(\text{dist}\) 都大於等於右兒子的 \(\text{dist}\)
因此,左偏樹中每個節點的 \(\text{dist}\) 是它右兒子的 \(\text{dist}\) 加一。

變數

int lson[N], rson[N], fa[N], fat[N];
ll val[N], dist[N];

lson: 左孩子(左偏);
rson: 右孩子;
fa: 父節點;
fat: 祖先(並查集);
val: 權值;
dist: 就是 \(\text{dist}\)

操作

  • 合併

int merge(int x, int y) { // 合併
	if (!x || !y) {
		return x | y;
	}
	if (val[x] > val[y] || (val[x] == val[y] && x > y))
		swap(x, y);
	rson[x] = merge(rson[x], y);
	fat[rson[x]] = fa[rson[x]] = x;
	if (dist[lson[x]] < dist[rson[x]])
		swap(lson[x], rson[x]);
	dist[x] = dist[rson[x]] + 1;
	return x;
}

if (!x || !y) { return x | y; } 如果與空節點合併,則直接合並即可
if (val[x] > val[y] || (val[x] == val[y] && x > y)) 說明這是個小根堆,小元素在上面。
if (dist[lson[x]] < dist[rson[x]]) swap(lson[x], rson[x]); 維護左偏的性質。

  • 刪除任意一個節點

左偏樹是不支援刪除給定權值的點的,只能刪除知道點的標號的點。

void earse(int u) { // 刪除任意一點
	int tmp = merge(lson[u], rson[u]), fu = fa[u];
	fat[tmp] = fa[tmp] = fu;
	fat[u] = fa[u] = tmp;
	lson[fu] == u ? lson[fu] = tmp : rson[fu] = tmp;
	while (fu) {
		if (dist[lson[fu]] < dist[rson[fu]])
			swap(lson[fu], rson[fu]);
		if (dist[fu] == dist[rson[fu]] + 1)
			return ;
		dist[fu] = dist[rson[fu]] + 1;
		fu = fa[fu];
	}
}

int tmp = merge(lson[u], rson[u]), fu = fa[u]; 先將被刪節點的左右孩子合併。
fat[tmp] = fa[tmp] = fu; 處理好父親和孩子的關係。

while (fu) {
	if (dist[lson[fu]] < dist[rson[fu]])
		swap(lson[fu], rson[fu]);
	if (dist[fu] == dist[rson[fu]] + 1)
		return ;
	dist[fu] = dist[rson[fu]] + 1;
	fu = fa[fu];
}

刪除點之後可能不符合左偏性質,需要我們向上修改,直到到根節點或符合左偏性質為止。

  • 查詢 \(u\) 點所在堆的堆頂元素的標號

這個操作類似於並查集操作。

int find(int u) { // 查詢堆頂的元素的標號
	return (fat[u] == u || fat[u] == 0) ? u : fat[u] = find(fat[u]);
}
  • 刪除 \(u\) 點所在堆的堆頂元素

void pop(int u) { // 彈出 u 點所在對的堆頂元素
	int g = find(u);
	earse(g);
}
  • 查詢 \(u\) 點所在堆的堆頂元素

ll top(int u) { // 查詢 u 點所在堆的堆頂元素
	int g = find(u);
	return val[g];
}
  • 建樹操作

int build(int n) { // 建樹
	queue<int> q;
	for (int i = 1; i <= n; ++ i) {
		q.push(i);
	}
	int x, y, z;
	while (q.size() > 1) {
		x = q.front(), q.pop();
		y = q.front(), q.pop();
		z = merge(x, y), q.push(z);
	}
	return q.front();
}

模板

// 左偏樹(小根堆)
struct leftist_tree {
	int lson[N], rson[N], fa[N], fat[N];
	ll val[N], dist[N];

	int merge(int x, int y) { // 合併
		if (!x || !y) {
			return x | y;
		}
		if (val[x] > val[y] || (val[x] == val[y] && x > y))
			swap(x, y);
		rson[x] = merge(rson[x], y);
		fat[rson[x]] = fa[rson[x]] = x;
		if (dist[lson[x]] < dist[rson[x]])
			swap(lson[x], rson[x]);
		dist[x] = dist[rson[x]] + 1;
		return x;
	}

	int find(int u) { // 查詢堆頂的元素的標號
		return (fat[u] == u || fat[u] == 0) ? u : fat[u] = find(fat[u]);
	}

	void earse(int u) { // 刪除任意一點
		int tmp = merge(lson[u], rson[u]), fu = fa[u];
		fat[tmp] = fa[tmp] = fu;
		fat[u] = fa[u] = tmp;
		lson[fu] == u ? lson[fu] = tmp : rson[fu] = tmp;
		while (fu) {
			if (dist[lson[fu]] < dist[rson[fu]])
				swap(lson[fu], rson[fu]);
			if (dist[fu] == dist[rson[fu]] + 1)
				return ;
			dist[fu] = dist[rson[fu]] + 1;
			fu = fa[fu];
		}
	}

	ll top(int u) { // 查詢 u 點所在堆的堆頂元素
		int g = find(u);
		return val[g];
	}

	void pop(int u) { // 彈出 u 點所在對的堆頂元素
		int g = find(u);
		earse(g);
	}

	int build(int n) { // 建樹
		queue<int> q;
		for (int i = 1; i <= n; ++ i) {
			q.push(i);
		}
		int x, y, z;
		while (q.size() > 1) {
			x = q.front(), q.pop();
			y = q.front(), q.pop();
			z = merge(x, y), q.push(z);
		}
		return q.front();
	}
};

pb_ds 中的堆

__gnu_pbds :: priority_queue

成員函數

push(): 向堆中壓入一個元素,返回該元素位置的迭代器。
pop(): 將堆頂元素彈出。
top(): 返回堆頂元素。
size(): 返回元素個數。
empty(): 返回是否非空。
modify(point_iterator, const key): 把迭代器位置的 key 修改為傳入的 key,並對底層儲存結構進行排序。
erase(point_iterator): 把迭代器位置的鍵值從堆中擦除。
join(__gnu_pbds :: priority_queue &other): 把 other 合併到 *this 並把 other 清空。