【模板】单源最短路径(标准版)

GitHub跳转原题关系图返回列表

用 heapq 实现带过期状态判断的 Dijkstra,求非负权有向图单源最短路。

OJ: luogu

题目 ID: P4779

难度:普及+/提高-

标签:Dijkstra最短路heapqpython

创建: 2026-07-17 03:00

更新: 2026-10-16T00:00:00.000Z

后置题目
luogu P3403跳楼机B 的 main.py 原样保留 A 教的堆优化 Dijkstra 步骤(含 current!=distance 时跳过过期条目)用于定出每个余数的最小楼层,B 在此之上叠加取模建余数图与 (h-dist[r])//base+1 计数。luogu P1875佳佳的魔法药水【数据有误】B 复用 A 的堆取当前最小距离并松弛的 Dijkstra 主循环,把单前驱出边松弛扩成两个前驱到齐才松弛的配方超边luogu P4568[JLOI2011] 飞行路线B 的 main.py 把 A 教的「堆优化 Dijkstra + 弹出时跳过过期条目」整体搬到分层状态 (city,used) 上(current != distance[used][node] 即 A 的惰性删除),再叠加 A 未教的免费次数进状态分层与 0 代价跨层转移。roj 3597文化之旅B 直接复用 A 教的堆优化 Dijkstra 作核心引擎,只把状态从单点扩成 (国家, 已学文化集合),额外叠加状压建模与 A* 剪枝。roj 3209「Sightseeing」 观光B 的两遍最短路(第一遍求 base、第二遍分层计数)都直接复用了 A 教的堆优化 Dijkstra:同样是堆里存“(权值, 结点)”按升序弹出最小状态并沿边松弛,A 用于单次单源最短路,B 把它套在拆点后的 2N 状态图上分发 ways,再叠加超额 0/1 建模与答案相加。roj 3070「Full Tank」 装满的油箱B 在分层图 (城市,剩油量) 的 Dial 桶队列最短路里原样复用 A 教的那一步过期判断——A 是 `current != distance[node]` 跳过堆中旧状态,B 换成 `dist[u][f] != idx` 跳过桶里过期条目,只在此模板上叠加 A 未教的分层状态设计与桶队列。roj 3071第K短路B 正解里 h_values 就是 A 教的堆优化 Dijkstra 原样搬到反图上(弹出最小距离、跳过过期条目、沿边松弛)作为 A* 的启发函数预处理;A 是纯模板,B 在此之上叠加 f=g+h 的 A* 检索与第 K 次弹出终点计数。roj 2063usaco-4.1.3 篱笆回路B 把 A 教的堆优化 Dijkstra 原样当作子程序(detour 里同样是弹出最小距离状态、沿边松弛、以 dictionary 距离表判重),只是额外叠加了把区域周长化为最小环、再逐条禁边求 w+dist 的最小值这一层流程。luogu P9724[EC Final 2022] Chase GameB 的正解第一段把 A 教的堆优化 Dijkstra 松弛出边原样复用,只是把边权换成 d-dist_k[v] 并在松弛时按 dist_k[v] 分流出首次传送判定luogu P3956[NOIP 2017 普及组] 棋盘B 把格子扩成 (x,y,color,magic) 状态后,直接复用 A 教的堆优化 Dijkstra 定出各状态最小花费,再叠加 0/1/2 三类边权转移与魔法不能连续使用的状态约束。luogu P2939[USACO09FEB] Revamping Trails GB 在改造次数分层的状态图上原样复用 A 的堆优化 Dijkstra 松弛步骤求最短时间luogu P4822[BJWC2012] 冻结B 把 A 教的「堆优化 Dijkstra(含弹出跳过过期条目)」整体搬到分层状态 (u,used) 的状态图上,再叠加 A 未教的用卡次数进状态分层与 w/2 转移。luogu P1186玛丽卡B 的正式做法把 A 教的堆优化 Dijkstra 当引擎反复调用(先求一条最短路并记录前驱,再逐条禁用最短路边重跑),再叠加只枚举最短路边的观察与回溯路径luogu P2914[USACO08OCT] Power Failure GB 直接复用 A 的 Dijkstra 核心步骤「每次取当前距离最小的状态、松弛所有出边」(B 因 n≤1000 改用朴素数组版),再叠加 A 未教的 0 权旧电线与距离不超过 M 才连欧氏边的建图、乘 1000 取整输出。luogu P2176[USACO11DEC] RoadBlock S / [USACO14FEB] Roadblock G/SB 复用 A 的「堆优化 Dijkstra 求单源最短路」这一步作为求解引擎(第一次求原最短路、候选边加倍后再跑),再叠加只枚举最短路上的边这一剪枝观察luogu P9751[CSP-J 2023] 旅游巴士B 沿用 A 的堆优化 Dijkstra 主循环与过期堆项跳过,把节点状态扩成(点,时刻 mod k)状态图,再叠加入口时刻整体推迟观察

题意

求非负权有向图中起点到每个节点的最短距离。

思路

Dijkstra 每次从堆中取当前距离最小的状态,尝试松弛所有出边。堆中可能保留旧状态,若弹出的距离不等于数组中的最新距离就跳过。

Python 知识

  • heapq 是最小堆,元组按距离优先比较。
  • current != distance[node] 是无 decrease-key 堆的标准过期判断。
  • print(*distance[1:]) 直接输出一行结果。

代码

标准写法:

python
import sys
from heapq import heappop, heappush


input = sys.stdin.buffer.readline
n, edges, start = map(int, input().split())
graph = [[] for _ in range(n + 1)]
for _ in range(edges):
    u, v, weight = map(int, input().split())
    graph[u].append((v, weight))

infinity = 10**30
distance = [infinity] * (n + 1)
distance[start] = 0
heap = [(0, start)]
while heap:
    current, node = heappop(heap)
    if current != distance[node]:
        continue
    for neighbor, weight in graph[node]:
        candidate = current + weight
        if candidate < distance[neighbor]:
            distance[neighbor] = candidate
            heappush(heap, (candidate, neighbor))
print(*distance[1:])

零基础逐行注释版(main-user.py),适合第一次用 heapq 写 Dijkstra 时对照阅读:

python
import sys
from heapq import heappop, heappush

# 边用 (终点 v, 边权 w) 表示
type Edge = tuple[int, int]
# 邻接表:g[u] 是出边列表,Graph 的每个元素本身也是列表
type Graph = list[list[Edge]]


def dijkstra(g: Graph, src: int) -> list[int]:
    infinity = 10**30
    # dis[v] 表示当前已知的 src 到 v 的最短距离,先全部设为无穷
    dis = [infinity for i in range(len(g))]
    dis[src] = 0

    # 小根堆,存放 (距离, 节点)。距离写在前面,元组比较时优先比距离
    heap = [(0, src)]

    while heap:
        disu, u = heappop(heap)
        # 堆里可能有同一个节点的多条旧记录,若弹出距离比 dis[u] 大说明已过期
        if disu > dis[u]:
            continue
        # 用 u 去松弛它的所有出边
        for v, w in g[u]:
            disv = disu + w
            # 只有能让 v 变短才更新,并把新状态压入堆
            if disv < dis[v]:
                dis[v] = disv
                heappush(heap, (disv, v))
    return dis


def solve(data):
    # 依次读出 n 个点、m 条边、起点 s
    n, m, s = next(data), next(data), next(data)
    # 邻接表用 1..n 编号,所以开 n+1 个位置
    g: list[list[Edge]] = [[] for _ in range(n + 1)]
    for _ in range(m):
        u, v, w = next(data), next(data), next(data)
        g[u].append((v, w))

    return dijkstra(g, s)


if __name__ == "__main__":
    # 一次读完输入,切成整数迭代器,比逐行 readline 更快
    data = iter(map(int, sys.stdin.buffer.read().split()))
    dis = solve(data)
    # 起点编号从 1 开始,跳过 dis[0];不可达的点仍为 infinity
    print(*dis[1:])

该版本用 type Edge = tuple[int, int]、type Graph = list[list[Edge]] 声明类型别名,阅读时可以直接看出邻接表是「点 → 出边列表」的两层结构,因此 g[u] 本身是列表、for v, w in g[u] 取出的才是边。注意 type 语句需要 Python 3.12 及以上;在旧版本上把它写成 Edge = tuple[int, int] 赋值形式即可。

原有 C++ 版本仍保留:

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

const int MAXN = 100000 + 5;
const int MAXM = 200000 + 5;
const long long INF = (1LL << 62);

int n, m, s;
int head[MAXN], to[MAXM], nxt[MAXM], edge_cnt;
long long weight_edge[MAXM];
long long dist_arr[MAXN]; // dist_arr[u] 表示从源点 s 到 u 的当前最短路
bool done[MAXN];

struct Node {
    long long dist;
    int u;
    bool operator<(const Node &other) const {
        return dist > other.dist;
    }
};

void add_edge(int u, int v, long long w) {
    edge_cnt++;
    to[edge_cnt] = v;
    weight_edge[edge_cnt] = w;
    nxt[edge_cnt] = head[u];
    head[u] = edge_cnt;
}

void dijkstra() {
    for (int i = 1; i <= n; i++) {
        dist_arr[i] = INF;
        done[i] = false;
    }

    priority_queue<Node> q;
    dist_arr[s] = 0;
    q.push({0, s});

    while (!q.empty()) {
        Node cur = q.top();
        q.pop();
        int u = cur.u;
        if (done[u]) {
            continue;
        }
        done[u] = true;

        for (int i = head[u]; i != 0; i = nxt[i]) {
            int v = to[i];
            long long w = weight_edge[i];
            if (dist_arr[v] > dist_arr[u] + w) {
                dist_arr[v] = dist_arr[u] + w;
                q.push({dist_arr[v], v});
            }
        }
    }
}

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

    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++) {
        int u, v;
        long long w;
        cin >> u >> v >> w;
        add_edge(u, v, w);
    }

    dijkstra();

    for (int i = 1; i <= n; i++) {
        if (i > 1) {
            cout << ' ';
        }
        cout << dist_arr[i];
    }
    cout << '\n';
    return 0;
}

复杂度

时间 O((n+m)log n),空间 O(n+m)。

总结

非负边权优先使用堆优化 Dijkstra。