左偏树
左偏树
概念
左偏树是一种常用可并堆, 可以在 \(\mathcal{O}(\log n)\) 的时间内合并两个堆.
可并堆顾名思义, 是指除了堆的普通操作, 还支持合并的堆. 有如配对堆, 二项堆, 斐波那契堆等等.
相关定义
- 外节点: 子节点数小于两个的节点, 即左右儿子至少有一个为空的节点.
- 距离 \(\rm{dist}\): 一个节点到它子树中离它最近的外节点的距离, 即两点间边的数量. 空节点的 \(\rm{dist} = 0\).
- 左偏树: 一种满足「左偏」性质的堆有序二叉树 (「左偏」性质体现在左儿子的 \(\rm{dist}\) 均大于 右儿子的 \(\rm{dist}\)).
- 左偏树的距离: 我们将一棵左偏树根节点的距离作为该树的距离.
性质
- 满足堆的基本性质.
- 对于任意节点, 左儿子的距离均大于右儿子的距离, 且该节点的距离等于其右儿子的距离加一.
- 对于一颗 \(n\) 个节点的左偏树, 其根节点的距离不超过 \(\log n\).
基本操作
以下的「堆」均默认为「小根堆」.
合并
合并是左偏树最核心的操作.
在合并两个堆时, 由于要满足堆的性质, 先取值较小的那个根作为合并后堆的根节点, 然后将这个根的左儿子作为合并后堆的左儿子, 递归地合并其右儿子与另一个堆, 来作为合并后的堆的右儿子. 为了满足左偏的性质, 若合并后左儿子的 \(\rm{dist}\) 大于右儿子的 \(\rm{dist}\), 就交换两个儿子.
插入节点
将节点视作一个堆, 进行合并操作即可.
删除根
合并根的左右儿子即可.
删除任意节点
这里的任意节点指的是任意编号的节点而并非任意权值的节点.
与删除根节点类似, 先将左右儿子合并, 再将合并后新的左偏树接到被删除节点的父节点上即可. 与删除根节点不同, 该操作可能导致左偏性质被破坏, 因此要从该节点一直向上检查左偏性质, 直到左偏性质没有被破坏或者到达了根节点.
代码
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
using namespace std;
void read() {}
template <class T, class ...T1>
void read(T &x, T1 &...y) {
x = 0;
char ch = getchar(); bool f = 1;
for (; ch < '0' or ch > '9'; ch = getchar()) if (ch == '-') f = 0;
for (; ch >= '0' and ch <= '9'; x = x * 10 + (ch & 15), ch = getchar());
x = (f ? x : -x);
read(y...);
}
constexpr int N = 1e5 + 10;
int n, m;
int dis[N], ls[N], rs[N], rt[N];
bool exist[N];
struct Node {
int id, v;
friend bool operator<(Node x, Node y) { return (x.v ^ y.v) ? x.v < y.v : x.id < y.id; }
} v[N];
void init() {
read(n, m);
dis[0] = -1;
for (int i = 1; i <= n; ++i) read(v[i].v), rt[i] = v[i].id = i;
}
int find(int x) { return rt[x] == x ? x : rt[x] = find(rt[x]); }
int merge(int x, int y) {
if (!x or !y) return x + y;
if (v[y] < v[x]) swap(x, y);
rs[x] = merge(rs[x], y);
if (dis[ls[x]] < dis[rs[x]]) swap(ls[x], rs[x]);
dis[x] = dis[rs[x]] + 1;
return x;
}
void calculate() {
while (m--) {
int op, x, y;
read(op, x);
if (op & 1) {
read(y);
if (exist[x] or exist[y]) continue;
x = find(x), y = find(y);
if (x ^ y) rt[x] = rt[y] = merge(x, y);
}
else {
if (exist[x]) {
puts("-1");
continue;
}
printf("%d\n", v[x = find(x)].v);
exist[x] = 1;
rt[ls[x]] = rt[rs[x]] = rt[x] = merge(ls[x], rs[x]);
ls[x] = rs[x] = dis[x] = 0;
}
}
}
void solve() {
init();
calculate();
}
int main() {
solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步