#6824. 2026/6/2/XMH笔记(Dij堆优化)

2026/6/2/XMH笔记(Dij堆优化)

堆优化 Dijkstra 复习速记

目标:考前快速复习、快速背模板、快速排错。
关键词:非负边权、单源最短路、小根堆、松弛


1. 用来解决什么?

Dijkstra:求一个起点到所有点的最短路。

适用条件:

所有边权 >= 0

不能处理:

负权边

2. 核心思想

每次从还没确定的点里,选一个当前距离起点最近的点,把它的最短路确定下来。

堆优化就是用 小根堆 快速找到这个点。

流程:

起点入堆
每次弹出 dist 最小的点 u
如果 u 已经确定,跳过
否则确定 u
用 u 去更新相邻点 v
如果 dist[v] 变短,就更新并重新入堆

3. 必背变量

struct Edge {
    int to, w;
};

vector<Edge> g[N]; // 邻接表
int dist[N];       // 起点到每个点的最短距离
bool vis[N];       // 这个点的最短路是否已经确定

堆里存:

{距离, 点编号}

所以写:

pq.push({dist[v], v});

4. 最重要的松弛

对于边:

u -> v,边权 w

如果经过 uv 更短:

if (dist[v] > dist[u] + w) {
    dist[v] = dist[u] + w;
    pq.push({dist[v], v});
}

记住:

最短路:能变小,就更新。

5. 标准模板 int 版

#include <bits/stdc++.h>
using namespace std;

const int N = 200010;
const int INF = 0x3f3f3f3f;

int n, m;
int dist[N];
bool vis[N];

struct Edge {
    int to, w;
};

vector<Edge> g[N];

void dijkstra(int s) {
    memset(dist, 0x3f, sizeof dist);
    memset(vis, 0, sizeof vis);

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

    dist[s] = 0;
    pq.push({0, s});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (vis[u]) continue;
        vis[u] = true;

        for (auto e : g[u]) {
            int v = e.to;
            int w = e.w;

            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;

    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;

        // 无向图
        g[a].push_back({b, c});
        g[b].push_back({a, c});
    }

    dijkstra(1);

    for (int i = 1; i <= n; i++) {
        if (dist[i] == INF) cout << "INF\n";
        else cout << dist[i] << "\n";
    }

    return 0;
}

6. long long 版怎么改?

如果边权大、路径长,改成 long long

主要改这几个地方:

const long long INF = 4e18;
long long dist[N];

struct Edge {
    int to;
    long long w;
};

priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;

其余逻辑不变。


7. 有向图 / 无向图

有向图:

g[a].push_back({b, c});

无向图:

g[a].push_back({b, c});
g[b].push_back({a, c});

8. 为什么要 vis

因为同一个点可能多次入堆。

比如堆里可能同时有:

{100, v}
{50, v}
{20, v}

最先弹出 {20, v},此时 v 的最短路确定。

后面的 {50, v}{100, v} 都是旧状态,要跳过:

if (vis[u]) continue;

9. 时间复杂度

常用记法:

O(m log n)

更严谨地说,普通 priority_queue 懒删除写法中,堆里可能有 O(m) 个元素,所以也可以写:

O(m log m)

但简单图中:

m <= n^2
log m <= 2 log n

所以竞赛里通常写:

O(m log n)

空间复杂度:

O(n + m)

10. 为什么堆操作是 log?

priority_queue 底层是二叉堆。

堆有 k 个元素时,高度约为:

log k

push 可能从底往上调整,pop 可能从顶往下调整,最多走一条高度。

所以:

push / pop = O(log k)

11. 不能处理负权边的原因

Dijkstra 每次会确定当前最近的点。

如果有负权边,后面可能通过负边让已经确定的点变得更短。

所以负权边会破坏 Dijkstra 的正确性。


12. 考前检查清单

[ ] 边权是否全部非负
[ ] 是否使用邻接表 vector<Edge>
[ ] Edge 是否是 to, w
[ ] 小根堆是否写了 greater<pair<int,int>>
[ ] 堆里是否存 {距离, 点}
[ ] dist 是否初始化为 INF
[ ] 起点 dist[s] 是否为 0
[ ] 是否写 if (vis[u]) continue
[ ] 是否写 vis[u] = true
[ ] 松弛条件是否是 dist[v] > dist[u] + w
[ ] 更新后是否重新入堆
[ ] 无向图是否加双向边
[ ] 答案大时是否使用 long long
[ ] 不可达点是否输出 INF

13. 最后只背这几句

Dijkstra 用于非负边权单源最短路。
堆里存 {距离, 点},用小根堆。
每次弹出当前距离最小的点。
访问过就跳过,没访问就确定。
枚举出边,能让 dist 变小就松弛。
复杂度常记 O(m log n)。

最核心代码:

if (dist[v] > dist[u] + w) {
    dist[v] = dist[u] + w;
    pq.push({dist[v], v});
}