- C20250069's blog
 可修改配对堆
- @ 2023-4-2 11:16:52
 
0. 什么是配对堆?
配对堆是一种堆型数据结构。它的特点是插入和求极值均是 。
它比较冷门,却有着极高的运行效率。
具体可以看看 OI Wiki。
1. 可修改?
一般的配对堆支持 decrease-key(OI Wiki 做法)。可网站上并没有说 increase-key。
这个就是今天主要要讲的。本文将简写“可修改配对堆”为“可改堆”。
2. 可改堆的功能?
| 名字 | 英文名(类中名) | 复杂度 | 
|---|---|---|
| 分离 | detach | 
|
| 合并 | merge | 
|
| 合并子节点 | merge_son | 
* | 
| 弹出节点 | pop_node | 
|
| 弹出堆顶 | pop | 
|
| 修改 | modify | 
|
| 增加节点 | push | 
|
| 求堆顶 | top | 
*不稳定复杂度。
3. 各种实现
本文以整形小根堆为例。
首先,我们先建一个类。
class Heap
{
public:
    struct HeapNode // 正常的配对堆
    {
        int val;
        HeapNode *fa, *ch, *br;
    } *dts[1000006];
    int len;
    Heap::HeapNode *head;
    Heap()
    {
        head = nullptr;
        len = 0;
    }
};
3.1 分离
这个操作在配对堆中没有,但却是可改堆中的重要一部分。
这个操作就是,将该节点从当前位置分离出来,且将其兄弟节点(如果有)替换到当前位置,而不破坏该节点与其子节点的相对位置。
举个例子:

对于这个以 A 为根的可改堆,我们对 C 进行分离操作,则会变成这样:

实现方式:
根据它的定义,直接写。
Heap::HeapNode *detach(Heap::HeapNode *hp)
    {
        if (hp == head) // 特判堆顶的情形
        {
            head = nullptr;
            return hp;
        }
        if (hp == nullptr) // 特判空指针的情形
        {
            return nullptr;
        }
        Heap::HeapNode **hf = hp->fa == nullptr ? nullptr : hp->fa->ch == hp ? &hp->fa->ch
                                                                             : &hp->fa->br; // 注意判断空指针
        if (hf != nullptr)
        {
            *hf = hp->br;
        }
        if (hp->br != nullptr)
        {
            hp->br->fa = hp->fa;
        }
        hp->fa = nullptr;
        return hp; // 返回分离后的节点
    }
3.2 合并
和配对堆合并基本一样。稍有不同就是合并时要先分离,合并后再加回去。这要求其中至少一个点是一个堆的堆顶。如:

我们要把 C 和 D 合并(假设 C 权值比 D 大),那么合并结果就是:

实现:
Heap::HeapNode *merge(Heap::HeapNode *a, Heap::HeapNode *b) // b 为一个堆的堆顶。
    {
        if (a == nullptr)
        {
            if (head == nullptr)
            {
                head = b;
                return head;
            }
            return nullptr;
        }
        Heap::HeapNode *af = a->fa;
        Heap::HeapNode **ac = af == nullptr ? nullptr : af->br == a ? &af->br
                                                                    : &af->ch; // 先记录相关值
        a = detach(a);
        if (a->val > b->val)
        {
            swap(a, b);
        }
        b->br = a->ch;
        if (a->ch != nullptr)
        {
            a->ch->fa = b;
        }
        a->ch = b;
        b->fa = a;
        if (head == nullptr)
        {
            head = a;
        }
        else if (ac != nullptr)
        {
            *ac = a;
        }
        return a; // 返回合并后的节点
    }
3.3 合并子节点
这和配对堆的合并就不一样了。配对堆是两两配对再合并,而可改堆子节点从后往前一个个合并。这个操作的时间复杂度,“均摊 ”参考 OI-Wiki。
例如:

我们对 C 进行合并子节点(设 E D F),则结果为:

Heap::HeapNode *merge_son(Heap::HeapNode *a)
    {
        if (a->ch == nullptr)
        {
            return nullptr;
        }
        if (a->ch->br == nullptr)
        {
            return detach(a->ch);
        }
        Heap::HeapNode *end = a->ch;
        while (end->br != nullptr) // 枚举到最后
        {
            end = end->br;
        }
        end = end->fa;
        Heap::HeapNode *msr = detach(end->br);
        while (end != a)
        {
            Heap::HeapNode *_tmp = end;
            end = end->fa;
            detach(_tmp);
            msr = merge(msr, _tmp);
        }
        return msr; // 子节点合并之后的堆顶
    }
3.4 弹出节点
这个也是可改堆与配对堆的重要不同点之一。
这个操作是:先将儿子合并(保证子树没有兄弟),然后(空则换成该节点的兄弟)替换到当前位置,将该节点断绝关系。
例如:

我们将 D 弹出(设 G H),则结果为:

或将 E 弹出,则结果为:

实现:还是根据定义直接写。
Heap::HeapNode *pop_node(Heap::HeapNode *a)
    {
        Heap::HeapNode *msr = merge_son(a);
        if (msr == nullptr) // 注意一堆(不是数据结构)特判
        {
            return detach(a);
        }
        if (a == head)
        {
            head = msr;
            a->ch = nullptr;
            return a;
        }
        msr->fa = a->fa;
        msr->br = a->br;
        Heap::HeapNode **ac = a->fa == nullptr ? nullptr : a->fa->br == a ? &a->fa->br
                                                                          : &a->fa->ch;
        if (ac != nullptr)
        {
            *ac = msr;
        }
        a->br->fa = msr;
        a->fa = a->ch = a->br = nullptr;
        return a; // 弹出的节点
    }
3.5 弹出堆顶
吾已有弹出节点,弹出堆顶岂不易乎?
Heap::HeapNode *pop()
    {
        return pop_node(head);
    }
3.6 修改
是不是这时候感觉没那么神秘了?
分三步:弹出,修改值,合并。
Heap::HeapNode *modify(Heap::HeapNode *a, int val)
    {
        a = pop_node(a);
        a->val = val;
        merge(head, a);
        return a;
    }
3.7 增加节点
这是个可加可不加的封装函数。其过程就是根据值建一个节点然后合并。
Heap::HeapNode *push(int a)
    {
        if (head == nullptr)
        {
            head = dts[len++] = (Heap::HeapNode *)malloc(sizeof(Heap::HeapNode));
            head->br = head->fa = head->ch = nullptr;
            head->val = a;
            return head;
        }
        else
        {
            Heap::HeapNode *_tmp = dts[len++] = (Heap::HeapNode *)malloc(sizeof(Heap::HeapNode));
            _tmp->br = _tmp->fa = _tmp->ch = nullptr;
            _tmp->val = a;
            merge(head, _tmp);
            return _tmp; // 返回建的节点的指针
        }
    }
3.8 求最值
根据可改堆的定义,堆顶即所求。
int top()
    {
        return head == nullptr ? 0 : head->val;
    }
至此,可改堆的所有功能都写完了!
完整版的代码:
class Heap
{
public:
    struct HeapNode
    {
        int val;
        HeapNode *fa, *ch, *br;
    } *dts[1000006];
    int len;
    Heap::HeapNode *head;
    Heap()
    {
        head = nullptr;
        len = 0;
    }
    Heap::HeapNode *detach(Heap::HeapNode *hp)
    {
        if (hp == head)
        {
            head = nullptr;
            return hp;
        }
        if (hp == nullptr)
        {
            return nullptr;
        }
        Heap::HeapNode **hf = hp->fa == nullptr ? nullptr : hp->fa->ch == hp ? &hp->fa->ch
                                                                             : &hp->fa->br;
        if (hf != nullptr)
        {
            *hf = hp->br;
        }
        if (hp->br != nullptr)
        {
            hp->br->fa = hp->fa;
        }
        hp->fa = nullptr;
        return hp;
    }
    Heap::HeapNode *merge(Heap::HeapNode *a, Heap::HeapNode *b)
    {
        if (a == nullptr)
        {
            if (head == nullptr)
            {
                head = b;
                return head;
            }
            return nullptr;
        }
        Heap::HeapNode *af = a->fa;
        Heap::HeapNode **ac = af == nullptr ? nullptr : af->br == a ? &af->br
                                                                    : &af->ch;
        a = detach(a);
        if (a->val > b->val)
        {
            swap(a, b);
        }
        b->br = a->ch;
        if (a->ch != nullptr)
        {
            a->ch->fa = b;
        }
        a->ch = b;
        b->fa = a;
        if (head == nullptr)
        {
            head = a;
        }
        else if (ac != nullptr)
        {
            *ac = a;
        }
        return a;
    }
    Heap::HeapNode *merge_son(Heap::HeapNode *a)
    {
        if (a->ch == nullptr)
        {
            return nullptr;
        }
        if (a->ch->br == nullptr)
        {
            return detach(a->ch);
        }
        Heap::HeapNode *end = a->ch;
        while (end->br != nullptr)
        {
            end = end->br;
        }
        end = end->fa;
        Heap::HeapNode *msr = detach(end->br);
        while (end != a)
        {
            Heap::HeapNode *_tmp = end;
            end = end->fa;
            detach(_tmp);
            msr = merge(msr, _tmp);
        }
        return msr;
    }
    Heap::HeapNode *pop_node(Heap::HeapNode *a)
    {
        Heap::HeapNode *msr = merge_son(a);
        if (msr == nullptr)
        {
            return detach(a);
        }
        if (a == head)
        {
            head = msr;
            a->ch = nullptr;
            return a;
        }
        msr->fa = a->fa;
        msr->br = a->br;
        Heap::HeapNode **ac = a->fa == nullptr ? nullptr : a->fa->br == a ? &a->fa->br
                                                                          : &a->fa->ch;
        if (ac != nullptr)
        {
            *ac = msr;
        }
        a->br->fa = msr;
        a->fa = a->ch = a->br = nullptr;
        return a;
    }
    Heap::HeapNode *pop()
    {
        return pop_node(head);
    }
    Heap::HeapNode *modify(Heap::HeapNode *a, int val)
    {
        a = pop_node(a);
        a->val = val;
        merge(head, a);
        return a;
    }
    Heap::HeapNode *push(int a)
    {
        if (head == nullptr)
        {
            head = dts[len++] = (Heap::HeapNode *)malloc(sizeof(Heap::HeapNode));
            head->br = head->fa = head->ch = nullptr;
            head->val = a;
            return head;
        }
        else
        {
            Heap::HeapNode *_tmp = dts[len++] = (Heap::HeapNode *)malloc(sizeof(Heap::HeapNode));
            _tmp->br = _tmp->fa = _tmp->ch = nullptr;
            _tmp->val = a;
            merge(head, _tmp);
            return _tmp;
        }
    }
    int top()
    {
        return head == nullptr ? 0 : head->val;
    }
};
4. 应用
4.1 【模版】堆
板子题,而且不需要修改操作。
// Luogu P3378 https://www.luogu.com.cn/problem/P3378
#include <iostream>
#include <cstdlib>
using namespace std;
class Heap
{}; // 省略 Heap 代码。内容和以上一样。
int main()
{
    Heap hp;
    int n;
    cin >> n;
    while (n--)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int x;
            cin >> x;
            hp.push(x);
        }
        else if (op == 2)
        {
            cout << hp.top() << endl;
        }
        else if (op == 3)
        {
            hp.pop();
        }
    }
    return 0;
}
4.2 zj
可以用可改堆来代替 priority_queue(这样不会 TLE ,当然会 pbds 也行)。因为某些原因,这里代码只写了使用部分,关于题目实现未贴上去。
#include <iostream>
using namespace std;
inline long long read()
{
}
#include <iostream>
// 复制的,反正重复 include 不会报错
#include <cstdlib>
using namespace std; // 重复 using 也一样
class Heap
{}; // 省略 Heap 代码。内容和以上一样。除了 dts 数组开到 7000006。
long long as;
long long a[10000007];
Heap::HeapNode *m[10000007];
Heap mt;
long long sm;
int main()
{
    for (int i = 1; i <= n; i++)
    {
        m[i] = mt.add(mi);
    }
    while (q--)
    {
        int op = read(), i, x;
        if (op == 1)
        {
        }
        else if (op == 2)
        {
            mt.modify(m[i], x);
        }
        else if (op == 3)
        {
            long long mm = mt.min();
        }
    }
    return 0;
}