堆 / 优先队列CSP-J

目录

💡 核心思想

堆是一种特殊的完全二叉树,满足堆性质:大根堆中每个节点的值 ≥ 子节点的值。C++ STL 的 priority_queue 默认是大根堆。支持 $O(\log n)$ 插入和取出最值,$O(1)$ 查看最值。常用于 Dijkstra、贪心、Top-K 等问题。

竞赛中几乎不需要手写堆——priority_queue 够用了。但你需要知道它的原理:上浮(push)和下沉(pop)。小根堆的声明是 priority_queue, greater>。堆的一个经典应用是"合并果子"(哈夫曼树的贪心构造)。

🎯 直觉理解

堆就像一个"自动排序"的队伍。无论你什么时候加人进来,最厉害的那个人(大根堆)或最弱的那个(小根堆)永远在最前面。你想找最值不用翻遍整个队伍,直接看第一个就行。

📝 算法流程

  1. C++ 默认大根堆:priority_queue
  2. 小根堆:priority_queue, greater>
  3. push:$O(\log n)$ 插入
  4. pop:$O(\log n)$ 取出最值

$$\text{堆操作:插入/删除 } O(\log n),\text{取最值 } O(1)$$

📊 复杂度分析

指标复杂度
时间插入/删除 $O(\log n)$,取最值 $O(1)$
空间$O(n)$

💻 参考实现(C++)

C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    priority_queue<int, vector<int>, greater<int>> pq; // 小根堆
    for (int i = 0; i < n; i++) { int x; cin >> x; pq.push(x); }
    long long ans = 0;
    while (pq.size() > 1) {
        int a = pq.top(); pq.pop();
        int b = pq.top(); pq.pop();
        ans += a + b;
        pq.push(a + b);
    }
    cout << ans << endl; // 合并果子
    return 0;
}

⚠️ 常见坑点

小根堆声明语法复杂容易写错

堆中存结构体需要重载 < 或传比较器

priority_queue 没有 decrease-key 操作(Dijkstra用懒惰删除代替)

自定义比较器时要确保严格弱序

📚 相关题目

题目来源难度备注
P1090 合并果子洛谷CSP-J贪心+堆
P1168 中位数洛谷CSP-S对堆求中位数