alpha

**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;
}
```

猜你喜欢

转载自blog.csdn.net/qq_40486466/article/details/84137037