**zlh. 的第 13 篇博客,别忘了点赞哦!**
支持快速合并的堆?配对堆帮你实现!
# 特色
配对堆是一种简单方便的可并堆,支持快速合并两个堆。
配对堆是一棵**多叉树**,树根总是堆中最小/大的值。
配对堆的特点在于效率高于其他大部分可并堆,而且代码简单,适合在考场上使用。
具体有多快?看看下面就知道了:
(顺便引出基本操作啦)
1. 合并两个堆(merge):$O(1)$
2. 压入一个值(push/insert):$O(1)$
3. 弹出堆顶(pop):$O(\log n)$
4. 取最值(top):$O(1)$
5. 删除任意节点(delete):$O(\log n)$
6. 修改任意节点的值(change):$O(1) / O(\log n)$
# 实现细节
配对堆一般使用 **左儿子右兄弟** 的表示方法。
什么?具体来说就是
```cpp
struct Node {
int val;
int son, bro;
} tree[MAXN];
```
其中 son 指向这个结点的第一个儿子, bro 指向这个结点的下一个兄弟,这样对于每个结点,它的儿子们都组成了一个链表。
下图中绿色的边代表 son 指针,蓝色边代表 bro 指针,红色的边是树中真正的边,但它们不是真实存在的。
![](https://cdn.luogu.org/upload/pic/43571.png)
# 功能详解
## 可并堆的核心:合并(merge)
配对堆可以在 $O(1)$ 的时间内美妙地合并两个堆:直接连接两个堆的堆顶,将优先级较小的堆置为优先级较大的堆的儿子,插入儿子链表中。
![](https://cdn.luogu.org/upload/pic/43600.png)
## 堆的基本操作 I:压入(push)
使用要压入的值创建一个新堆,将这个堆与原堆合并即可
![](https://cdn.luogu.org/upload/pic/43602.png)
## 堆的基本操作 II:弹出(pop)
若要弹出一个堆的堆顶,先将这个堆顶删除,然后将剩下的所有子树合并起来。
如果直接一个一个合并的话,有可能会出现一个结点拥有很多儿子的情况,使下次 pop 操作往 $O(n)$ 退化。
一种解决方法是先将相邻的结点**配对合并**,使子树的个数减少到 $\dfrac{n}{2}$ 个后,再直接合并。
这样就可以保证均摊的时间复杂度为 $O(\log n)$,不至于退化。
![](https://cdn.luogu.org/upload/pic/43606.png)
## 堆的基本操作 III:取最值(top)
记录一个 root ,返回 tree[root].val 即可。
![](https://cdn.luogu.org/upload/pic/43613.png)
# 代码一览
delete 操作和 change 操作需要维护父指针,较为麻烦,鉴于篇幅~~(才不告诉你是作者太弱)~~,这里没有给出这两个操作的代码。
## 链表建树:
```cpp
struct Node {
int val;
int bro, son;
} tree[MAXN];
```
## 新建结点(newnode):
```cpp
int newnode(int k) {
node[++ node_cnt].val = k;
return node_cnt;
}
```
## 合并(merge):
```cpp
int merge(int x, int y) {
if(!x || !y)
return x | y;
else {
if(tree[x].val > tree[y].val)
swap(x, y);
tree[y].bro = tree[x].son;
tree[x].son = y;
return x;
}
}
```
## 压入(push):
```cpp
void push(int x) {
root = merge(root, newnode(x));
}
```
## 弹出(pop):
弹出需要一个辅助函数 `merge_ch(x)` ,表示将 x 的所有兄弟合并,返回新得到的根。
使用递归的方法合并,令 y 为 x 的下一个兄弟, z 是 y 的下一个兄弟,先将 x 和 y 合并得到 a ,再将 z 的兄弟合并得到 b ,最后合并 a 和 b 即可。
![merge_ch](https://cdn.luogu.org/upload/pic/43608.png)
如果认为递归可能爆栈,可以改成非递归版
```cpp
int merge_ch(int x) {
if(!x)
return 0;
int y = tree[x].bro;
int z = tree[y].bro;
node[x].bro = tree[y].bro = 0;
return merge(merge(x, y), merge_ch(z));
}
```
注意 merge_ch 中要将 x 和 y 的 bro 清零,虽然不清零不会影响答案正确性,但为了保险与严谨起见,最好还是清零一下。
这样, pop 函数只需将根的儿子们都合并即可
```cpp
void pop() {
root = merge_ch(tree[root].son);
}
```
# 代码
这是一份简洁的代码,可以在 [P3378 【模板】堆](https://www.luogu.org/problemnew/show/P3378) 上AC。
```cpp
#include<bits/stdc++.h>
using namespace std;
struct Pairing_Heap {
static const int MAXN = 1000000 + 10;
struct Node {
int val;
int bro, son;
} tree[MAXN];
int node_cnt, root;
int newnode(int k) {
tree[++ node_cnt].val = k;
return node_cnt;
}
int merge(int x, int y) {
if(!x || !y)
return x | y;
else {
if(tree[x].val > tree[y].val)
swap(x, y);
tree[y].bro = tree[x].son;
tree[x].son = y;
return x;
}
}
int merge_ch(int x) {
if(!x)
return 0;
int y = tree[x].bro;
int z = tree[y].bro;
tree[x].bro = tree[y].bro = 0;
return merge(merge(x, y), merge_ch(z));
}
void push(int x) {
root = merge(root, newnode(x));
}
int top() {
return tree[root].val;
}
void pop() {
root = merge_ch(tree[root].son);
}
} H;
int n;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
int mode, x;
scanf("%d", &mode);
if(mode == 1) {
scanf("%d", &x);
H.push(x);
}
else if(mode == 2) {
printf("%d\n", H.top());
}
else {
H.pop();
}
}
return 0;
}
```