用 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。