左偏树 学习笔记
- 前置知识:堆
定义
左偏树是 可并堆 的一种。除了支持堆的所有操作之外,左偏树还支持合并两个堆并把复杂度维持在 \(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\)。合并过程如下:
- 对于这两个堆,合并后的堆顶为 \(x\)。
- 令新堆顶 \(x\) 的左子树为 \(x\) 的左子树(其实就是左子树不变)。
- 递归合并 \(x\) 的右子树这个「子堆」和 \(y\),作为新堆顶 \(x\) 的右子树。
- 回溯时检查 \(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;
}

浙公网安备 33010602011771号