最短路算法

First Post:

Last Update:

Word Count:
865

Read Time:
4 min

Page View: loading...

记号约定

不一定是通用用法。

  • 表示图的点数();
  • 表示图的边数();
  • 有边(将边记作 ),则 表示其边权;若 没有边,记
  • 有路径(将路径记作 ),则记 为路径长度;
  • 有最短路,则 表示其最短路;特别地,若 经过负环,则 记为 ;若 没有路径,则 记为 ;

Johnson 全源最短路径算法

今天学了个不常用的玩意,因此先记下来,其他最短路算法有空再补。

算法的核心思想是构造势能函数 ,然后对于边 ,将边权重新标记为

容易证明,对于任意路径 ,其新路径长度为

因此其最短路长度变为

假如我们可以设计函数使得标记后的边权 非负,则可以通过 次 Dijkstra 算法计算全源最短路,时间复杂度为 (常见优先队列写法)。

注意到,图论中存在三角不等式 ,因此可以使用

在图中任取一个点可能遇到 的情形,因此我们新建一个超级源点 ,并向每个点连一条边权为 的边,并取

可以使用 Bellman–Ford 算法或其队列优化版本(SPFA)计算单源最短路(此时顺便可以检查图中是否存在负环),显然这部分最劣情况下时间复杂度 不成为复杂度瓶颈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <queue>
#include <utility>
#include <vector>
using namespace std;

static const int64_t MAXV = 1e9;

vector<int64_t> spfa(size_t s,
const vector<vector<pair<size_t, int64_t>>> &adj) {
vector<size_t> cnt(adj.size(), 0);
vector<int64_t> dist(adj.size(), MAXV);
dist[s] = 0;
vector<bool> in_queue(adj.size(), false);
queue<size_t> q;
q.push(s);
in_queue[s] = true;
while (!q.empty()) {
size_t u = q.front();
q.pop();
in_queue[u] = false;
for (const auto &[v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] > adj.size())
return {};
if (!in_queue[v]) {
q.push(v);
in_queue[v] = true;
}
}
}
}
return dist;
}

vector<int64_t> dijkstra(size_t s,
const vector<vector<pair<size_t, int64_t>>> &adj) {
vector<int64_t> dist(adj.size(), MAXV);
vector<bool> visited(adj.size(), false);
priority_queue<pair<int64_t, size_t>> q;
q.emplace(0, s);
dist[s] = 0;
while (!q.empty()) {
auto [d, u] = q.top();
q.pop();
if (visited[u])
continue;
visited[u] = true;
for (const auto &[v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
q.emplace(-dist[v], v);
}
}
}
return dist;
}

int main() {
// Input
cin.tie(nullptr)->sync_with_stdio(false);
size_t n, m;
cin >> n >> m;
vector<vector<pair<size_t, int64_t>>> adj(n + 1);
for (size_t i = 1; i <= n; ++i)
adj[0].emplace_back(i, 0);
for (size_t i = 0; i < m; ++i) {
size_t u, v;
int64_t w;
cin >> u >> v >> w;
adj[u].emplace_back(v, w);
}

auto H = spfa(0, adj);
if (H.empty()) {
cout << "-1\n";
} else {
for (size_t u = 1; u <= n; ++u)
for (auto &[v, w] : adj[u])
w += H[u] - H[v];

vector<vector<int64_t>> dist;
dist.reserve(n + 1);
dist.emplace_back();
for (size_t i = 1; i <= n; ++i)
dist.emplace_back(dijkstra(i, adj));

for (size_t i = 1; i <= n; ++i)
for (size_t j = 1; j <= n; ++j)
if (dist[i][j] != MAXV)
dist[i][j] += H[j] - H[i];

// Output
for (size_t i = 1; i <= n; ++i) {
int64_t ret = 0;
for (size_t j = 1; j <= n; ++j)
ret += dist[i][j] * j;
cout << ret << '\n';
}
}
return 0;
}

如果 Dijkstra 使用斐波那契堆实现,则单次 Dijkstra 的时间复杂度为 ,总的时间复杂度变为