Loading

左偏树 学习笔记

  • 前置知识:堆

定义

左偏树是 可并堆 的一种。除了支持堆的所有操作之外,左偏树还支持合并两个堆并把复杂度维持在 \(O(n \log n)\) 级别。

左偏树的实现

Dist

外节点: 对于一棵二叉树,我们定义外节点为左儿子或右儿子为空的节点。

dist: 我们定义某一个节点的 \(\text{dist}\) 为该点到最近的外节点的距离。外节点的 \(\text{dist}\)\(1\)

我们不难发现,一个有 \(n\) 个节点的堆,根的 \(\text{dist}\) 不超过 \(\left\lceil\log(n+1)\right\rceil\)。假设一个根的 \(\text{dist}\)\(d\),显然它至少有 \(2^d-1\) 个节点。取对数即得到上述结论。

左偏树为何左偏

之所以叫「左偏树」,因为在左偏树中,对于任何一个节点,左儿子的 \(\text{dist}\) 一定大于右儿子的 \(\text{dist}\)

正因为如此,左偏树中任何一个节点的 \(\text{dist}\) 为右儿子的 \(\text{dist}\)\(1\)

左偏树的操作

  • Merge

合并两个堆是一个递归的过程。我们令当前要合并的的两个堆堆顶分别为 \(x,y\)\(val_x<val_y\)。合并过程如下:

  1. 对于这两个堆,合并后的堆顶为 \(x\)
  2. 令新堆顶 \(x\) 的左子树为 \(x\) 的左子树(其实就是左子树不变)。
  3. 递归合并 \(x\) 的右子树这个「子堆」和 \(y\),作为新堆顶 \(x\) 的右子树。
  4. 回溯时检查 \(x\) 左儿子和右儿子的 \(\text{dist}\)。若左儿子的 \(\text{dist}\) 小于右儿子的 \(\text{dist}\) 则交换两个儿子。

参考代码:

int merge(int x, int y) {
	if(!x || !y) return x | y;
	if(node[x].val > node[y].val || (node[x].val == node[y].val && x > y)) swap(x, y);
	node[x].rs = merge(node[x].rs, y);
	if(node[node[x].ls].dist < node[node[x].rs].dist) swap(node[x].ls, node[x].rs);
	node[x].dist = node[node[x].rs].dist + 1;
	return x;
}
  • Pop

有了 Merge 的操作,我们 Pop 就不必那么麻烦了。如果我们想弹出一个点 \(x\),只需要合并 \(x\) 的左右子树然后把 \(x\) 删掉。

  • Insert

同理,我们插入节点时也可以直接把待插入节点 \(x\) 当成一个堆,直接执行 Merge 操作即可。

  • Find

我们常常需要照到某个点 \(x\) 的堆顶 \(rt\)。如果直接向上跳单次复杂度可能退化到 \(O(n)\)。为了避免这种情况的发生,我们需要用并查集维护点在哪个堆里面。具体操作和并查集类似。

需要注意的是为了维护 father 关系,我们执行 Merge 时要把合并的两个堆顶节点 \(x,y\) 的父亲都设为 merge 函数的返回值(即新的堆顶)。考虑到路径压缩的存在,执行 Pop 操作时还要把被 Pop 的节点的父亲设为其两个儿子 merge 后的新堆顶。

总代码

Luogu 上的模板题,要求支持 Merge 和 Pop 的操作。

struct Node {
	int fa, val, dist;
	int ls, rs;
}
node[Maxn]; int n, m;

int find(int x) {
	if(x == node[x].fa) return x;
	else return node[x].fa = find(node[x].fa);
}

int merge(int x, int y) {
	if(!x || !y) return x | y;
	if(node[x].val > node[y].val || (node[x].val == node[y].val && x > y)) swap(x, y);
	node[x].rs = merge(node[x].rs, y);
	if(node[node[x].ls].dist < node[node[x].rs].dist) swap(node[x].ls, node[x].rs);
	node[x].dist = node[node[x].rs].dist + 1;
	return x;
}

void pop(int x) {
	node[x].val = -1;
	node[node[x].ls].fa = node[node[x].rs].fa = node[x].fa = merge(node[x].ls, node[x].rs);
}


int main() {
	n = read(); m = read();
	for(int i = 1; i <= n; ++i) {
		node[i].fa = i;
		node[i].val = read();
		node[i].dist = 1;
	}
	int op, x, y;
	for(int i = 1; i <= m; ++i) {
		op = read();
		if(op == 1) {
			x = read(); y = read();
			if(node[x].val == -1 || node[y].val == -1) continue;
			x = find(x); y = find(y);
 			if(x != y) node[x].fa = node[y].fa = merge(x, y);
		}
		if(op == 2) {
			x = read();
			if(node[x].val == -1) {printf("-1\n"); continue;}
			printf("%d\n", node[find(x)].val); pop(find(x));
		}
	}
	return 0;
}
posted @ 2020-08-02 16:44  Sqrtyz  阅读(107)  评论(0)    收藏  举报