Dijkstra
2017-07-21Bài gốc
Đề bài
Cho đồ thị vô hướng n đỉnh và m cạnh, mỗi cạnh có trọng số không âm w. Tìm đường đi ngắn nhất từ đỉnh 1 đến n.
Đồ thị có thể có vòng lặp, và có nhiều cạnh nối một cặp đỉnh.
Giới hạn:
- 2 ≤ n ≤ 105; 0 ≤ m ≤ 105
- 1 ≤ wi ≤ 106
Định dạng test
Input:
- Dòng đầu là n và m.
- m dòng tiếp theo; mỗi dòng 3 số u, v, w: cạnh nối đỉnh u và v có trọng số w
Các đỉnh được đánh số từ 1 đến n.
Output:
- Nếu không tìm được đường đi ngắn nhất, in -1
- Nếu có, in hai dòng:
- Dòng đầu là chi phí nhỏ nhất.
- Dòng thứ hai là đường đi ngắn nhất.
Ví dụ:
Thuật toán
Source code
Ngôn ngữ: C++.
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
typedef long long ll;
typedef pair<ll, int> lli;
const int N = 1e5 + 5;
const ll oo = 1e9;
int n, m, trace[N];
vector<lli> a[N];
vector<ll> dist;
priority_queue<lli, vector<lli>, greater<lli>> Q;
void print_path(int u) {
if (u == 0) return;
print_path(trace[u]);
cout << u << ' ';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// read input
cin >> n >> m;
REP(i,m) {
int u, v; ll w;
cin >> u >> v >> w;
a[u].pb(mp(w, v));
a[v].pb(mp(w, u));
}
// init for dijkstra
dist.assign(n+1, oo);
dist[1] = 0;
Q.push(mp(0, 1));
// dijkstra
while(!Q.empty()) {
lli p = Q.top(); Q.pop();
int u = p.se;
ll d = p.fi;
if (d > dist[u]) continue;
for(lli _ : a[u]) {
int v = _.se;
if (dist[u] + _.fi < dist[v]) {
trace[v] = u;
dist[v] = dist[u] + _.fi;
Q.push(mp(dist[v], v));
}
}
}
// output
if (dist[n] == oo)
cout << -1;
else {
cout << dist[n] << '\n';
print_path(n);
}
return 0;
}
Tính chất và ứng dụng
Sau khi chạy xong thuật toán Dijkstra, dist[u] chính là chi phí nhỏ nhất để đi từ đỉnh ban đầu là 1 đến u.
Một bài toán phổ biến: kiểm tra xem một đỉnh hoặc một cạnh của đồ thị có nằm trên đường đi ngắn nhất từ s đến t hay không? Đầu tiên sử dụng Dijkstra để xây dựng ds[u] là khoảng cách ngắn nhất từ s đến u, và dt[u] là khoảng cách ngắn nhất từ u đến t:
- Xét đỉnh u, nếu có ds[u] + dt[u] = ds[t] (= dt[s]) thì u thuộc đường đi ngắn nhất từ s đến t.
- Xét cạnh (u, v), có 2 khả năng cho con đường ngắn nhất từ s đến t và đi qua (u, v):
- s -> u -> v -> t, tổng chi phí: ds[u] + w(u, v) + dt[v].
- t -> u -> v -> s, tổng chi phí: st[u] + w(u, v) + ds[v].
- Nếu một trong hai chi phí trên bằng ds[t] (hoặc dt[s]) thì cạnh (u, v) thuộc đường đi ngắn nhất từ s đến t.
Một bài toán khác: tính f[u] là số con đường có chi phí ngắn nhất khi đi từ s đến u, có thể định dạng đoạn code trên một chút như sau:
- Khởi tạo:
1
2
3
4
// init for dijkstra
...
f.assign(n+1, 0);
f[1] = 1;
- Truy hồi:
1
2
3
4
5
6
7
8
9
10
// dijkstra
while(!Q.empty()) {
...
if (dist[u] + _.fi < dist[v]) {
...
f[v] = f[u];
}
else if (dist[u] + _.fi == dist[v])
f[v] += f[u];
}
Độ phức tạp
O(ElogE), với |E| là số cạnh của đồ thị.