参考:hsfzLZH1 的 博客 及代码启示
左偏树
模版题:Luogu3377。
由于朴素堆的合并操作可能会退化到
一些定义
该部分改编自 hsfzLZH1 的 博客
外结点 :左儿子或右儿子是空结点的结点(首先左偏树是一个堆,堆是一棵二叉树)。
距离 : 一个结点
基本性质
- 堆性质:与普通堆相同,以小根堆为例,其满足每个结点的关键字一定小于等于其左右儿子。
- 左偏性:对于每个结点
,有 ( 为 结点的左儿子, 为 结点的右儿子,下同)
容易发现,左偏树的每一个儿子的左子树的结点数量一定不小于右子树的结点数量。这为我们的合并操作提供了便利。
基本结论
。- 从根结点开始,一直往右儿子走,走到空结点的复杂度是
。
知道这两个结论就足够了。
如果你还有兴趣的话,这里补充一个性质:距离为
的左偏树至少有 个结点。此时该左偏树的形态是一棵满二叉树。
合并操作
合并两棵分别以
在合并过程中,如果我们发现以
容易发现合并后的树后的树仍然满足堆性质和左偏性,且该操作的复杂度是
维护结点所在左偏树的根结点
经常地,题目会给定一个点,需要你求出其所在左偏树的根节点再进行后续操作。
我们可以记录每一个的父亲节点
inline int find(int x) { if (rt[x] == x) return x; return rt[x] = find(rt[x]); }
这时候我们需要对合并操作和删除操作进行操作。
删除一个点,就是对于一个点合并其左右儿子即可,非常简单。
- 合并结点
时,我们只需要rt[x]=rt[y]=merge(x,y)
。 - 删除结点
时,我们只需要rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x])
注意,再删除操作时,我们需要将
一些简单的其他操作
- 插入一个值:等价于与一棵只有一个结点的树合并,唯一结点的权值等于插入的值。
- 求最小值:直接返回根结点的值即可。
- 删除最小值:合并根节点的左右儿子即可(记得删除时需要维护一些需要维护的信息)。
简单地说,左偏树就是一个将合并操作复杂度降至
几道练手题
Luogu2713 罗马游戏 纯模板,学会左偏树后可以直接解决。
Luogu1552 [APIO2012] 派遣 有一定思维难度的一道可并堆,利用可并堆解决问题的一道典例。
Luogu1456 Monkey King 可并堆题,但是有一个比较有趣的操作,可以锻炼对可并堆的变换能力,比较简单。
代码
Luogu3377 【模板】左偏树(可并堆)
#include<bits/stdc++.h> using namespace std; const int N = 1e5 + 5; struct Heap { int val, id, ls, rs; bool operator <(const Heap &x)const { if (val == x.val) return id < x.id; return val < x.val; }; } tree[N]; int n, m, fa[N], fl[N], dis[N]; inline int find(int x) { if (fa[x] == x) return x; return fa[x] = find(fa[x]); } inline int merge(int x, int y) { if (!x || !y) return x + y; if (tree[y] < tree[x]) swap(x, y); tree[x].rs = merge(tree[x].rs, y); if (dis[tree[x].ls] < dis[tree[x].rs]) swap(tree[x].ls, tree[x].rs); dis[x] = dis[tree[x].rs] + 1; return x; } int main() { dis[0] = -1; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &tree[i].val); tree[i].id = i; fa[i] = i; } for (int i = 1, opt, x, y; i <= m; i++) { scanf("%d%d", &opt, &x); if (opt == 1) { scanf("%d", &y); if (fl[x] || fl[y]) continue; x = find(x); y = find(y); if (x != y) fa[x] = fa[y] = merge(x, y); } else { if (fl[x]) { printf("-1\n"); continue; } x = find(x); printf("%d\n", tree[x].val); fl[x] = true; fa[tree[x].ls] = fa[tree[x].rs] = fa[x] = merge(tree[x].ls, tree[x].rs); } } return 0; }