Đường đi ngắn nhất
BFS tìm đường đi ngắn nhất khi mọi cạnh dài như nhau. Khi các cạnh có độ dài khác nhau (đường bộ dài ngắn khác nhau, chi phí khác nhau), cần thuật toán khác. Chọn thuật toán nào phụ thuộc vào độ dài cạnh có âm không và cần khoảng cách giữa những cặp đỉnh nào.
| Tình huống | Thuật toán | Số bước |
|---|---|---|
| mọi cạnh dài 1 | BFS | n + m |
| cạnh dài 0 hoặc 1 | BFS 0-1 | n + m |
| cạnh không âm, từ một đỉnh | Dijkstra | khoảng m × log2n |
| có cạnh âm, từ một đỉnh | Bellman-Ford | n × m |
| mọi cặp đỉnh, n nhỏ (vài trăm) | Floyd-Warshall | n3 |
Dijkstra
Phần tiêu đề “Dijkstra”Giữ kc[v] là độ dài đường ngắn nhất đã biết tới v. Mỗi bước lấy ra đỉnh u chưa chốt có kc nhỏ nhất và chốt nó: kc[u] chắc chắn là đáp số. Rồi thử đi tiếp qua các cạnh u → v: nếu kc[u] + w nhỏ hơn kc[v] thì cập nhật kc[v].
Vì sao chốt được? Mọi đường khác tới u đều phải đi qua một đỉnh chưa chốt v nào đó, mà kc[v] ≥ kc[u] và các cạnh không âm, nên đường đó không thể ngắn hơn kc[u]. Lý luận này sai khi có cạnh âm: đi xa hơn rồi qua cạnh âm có thể quay lại ngắn hơn.
Dùng hàng đợi ưu tiên để lấy đỉnh có kc nhỏ nhất. Mỗi lần cập nhật thì đẩy cặp (kc mới, v) vào; khi lấy ra mà kc ghi trong cặp lớn hơn kc[v] hiện tại thì đó là cặp cũ, bỏ qua.
import sysimport heapqdu_lieu = sys.stdin.read().split()n, m = int(du_lieu[0]), int(du_lieu[1])ke = [[] for _ in range(n + 1)]for i in range(m): u, v, w = int(du_lieu[2 + 3 * i]), int(du_lieu[3 + 3 * i]), int(du_lieu[4 + 3 * i]) ke[u].append((v, w)) ke[v].append((u, w))VO_CUNG = float('inf')kc = [VO_CUNG] * (n + 1)kc[1] = 0dong = [(0, 1)]while dong: d, u = heapq.heappop(dong) if d > kc[u]: # cặp cũ, u đã được chốt với kc nhỏ hơn continue for v, w in ke[u]: if d + w < kc[v]: kc[v] = d + w heapq.heappush(dong, (kc[v], v))print(*[x if x < VO_CUNG else -1 for x in kc[1:]])#include <iostream>#include <vector>#include <queue>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<vector<pair<int, long long>>> ke(n + 1); for (int i = 0; i < m; i++) { int u, v; long long w; cin >> u >> v >> w; ke[u].push_back({v, w}); ke[v].push_back({u, w}); } const long long VO_CUNG = 4e18; vector<long long> kc(n + 1, VO_CUNG); priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<>> dong; kc[1] = 0; dong.push({0, 1}); while (!dong.empty()) { auto [d, u] = dong.top(); dong.pop(); if (d > kc[u]) continue; // cặp cũ for (auto [v, w] : ke[u]) if (d + w < kc[v]) { kc[v] = d + w; dong.push({kc[v], v}); } } for (int u = 1; u <= n; u++) cout << (kc[u] < VO_CUNG ? kc[u] : -1) << (u < n ? ' ' : '\n'); return 0;}BFS 0-1
Phần tiêu đề “BFS 0-1”Khi mỗi cạnh dài 0 hoặc 1 (ví dụ đi thẳng miễn phí, rẽ hoặc phá tường tốn 1), dùng deque: đi cạnh dài 0 thì đẩy đỉnh vào đầu deque, cạnh dài 1 thì đẩy vào cuối. Deque luôn chứa các đỉnh theo khoảng cách không giảm, chỉ có hai mức liền nhau, nên đây là Dijkstra mà không cần hàng đợi ưu tiên: n + m bước.
Bellman-Ford
Phần tiêu đề “Bellman-Ford”Khi có cạnh âm (nhưng không có chu trình âm), lặp n - 1 lượt, mỗi lượt thử mọi cạnh u → v: kc[v] = min(kc[v], kc[u] + w). Đường ngắn nhất đi qua nhiều nhất n - 1 cạnh, và sau lượt thứ k thì mọi đường ngắn nhất dùng tối đa k cạnh đã được tính đúng.
Nếu ở lượt thứ n vẫn còn cạnh làm kc giảm thì có chu trình âm: đi vòng quanh nó mãi thì đường ngày càng ngắn, không có đường ngắn nhất.
Floyd-Warshall
Phần tiêu đề “Floyd-Warshall”Khoảng cách giữa mọi cặp đỉnh: kc[i][j] ban đầu là độ dài cạnh (hoặc vô cùng). Lần lượt cho phép đi qua đỉnh trung gian k = 1, 2, …, n:
kc[i][j] = min(kc[i][j], kc[i][k] + kc[k][j])
Ba vòng lặp lồng nhau, vòng k ở ngoài cùng. Sau khi xét xong k, kc[i][j] là đường ngắn nhất chỉ dùng các đỉnh trung gian trong 1, …, k. n = 400 là 6,4 × 107 bước, hợp với C++; trong Python nên giữ n khoảng 150 trở xuống.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 16 bài, chia thành 4 nhóm từ dễ đến khó. Mỗi bài có một dữ liệu kiểm tra: các bạn chạy chương trình của mình với dữ liệu đó rồi so với đáp số.
Nhóm 1. Dijkstra
Phần tiêu đề “Nhóm 1. Dijkstra”Bài 1. Khoảng cách từ thành phố 1
Phần tiêu đề “Bài 1. Khoảng cách từ thành phố 1”Có n thành phố, m con đường hai chiều, đường thứ i nối u, v và dài w. Dòng đầu có n, m (1 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 109). In ra quãng đường ngắn nhất từ thành phố 1 tới các thành phố 1, 2, …, n (-1 nếu không tới được).
Ví dụ:
| Nhập | In ra |
|---|---|
5 61 2 41 3 13 2 22 4 53 4 84 5 3 | 0 3 1 8 11 |
Kiểm tra: nhập
7 91 2 71 3 91 6 142 3 102 4 153 4 113 6 26 5 94 5 6Bài 2. Bệnh viện gần nhất
Phần tiêu đề “Bài 2. Bệnh viện gần nhất”Có n khu dân cư, m con đường hai chiều có độ dài, và k bệnh viện đặt ở k khu. Dòng đầu có n, m, k (1 ≤ k ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), dòng thứ hai có k số là các khu có bệnh viện, m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 109). In ra quãng đường từ mỗi khu 1, 2, …, n tới bệnh viện gần nhất (-1 nếu không tới được bệnh viện nào).
Ví dụ:
| Nhập | In ra |
|---|---|
5 4 21 51 2 32 3 43 4 14 5 2 | 0 3 3 2 0 |
Kiểm tra: nhập
7 8 22 61 2 52 3 23 4 84 5 15 6 36 7 41 7 23 6 6Bài 3. Đi qua thành phố bắt buộc
Phần tiêu đề “Bài 3. Đi qua thành phố bắt buộc”Có n thành phố, m con đường hai chiều có độ dài. Phải đi từ thành phố 1 tới thành phố n và ghé qua thành phố k. Dòng đầu có n, m, k (2 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105, 1 ≤ k ≤ n), m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 109). In ra quãng đường ngắn nhất, hoặc -1.
Ví dụ:
| Nhập | In ra |
|---|---|
5 6 41 2 22 5 31 3 13 4 24 5 62 4 1 | 7 |
Kiểm tra: nhập
7 9 61 2 42 3 13 7 21 4 64 5 35 6 26 7 52 5 74 6 9Bài 4. Đếm số đường ngắn nhất
Phần tiêu đề “Bài 4. Đếm số đường ngắn nhất”Có n thành phố, m con đường một chiều có độ dài. Dòng đầu có n, m (2 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w (đường u → v, 1 ≤ w ≤ 109). In ra độ dài đường ngắn nhất từ 1 tới n và số đường ngắn nhất như vậy chia lấy dư 109 + 7; nếu không tới được in -1 0.
Ví dụ:
| Nhập | In ra |
|---|---|
4 51 2 11 3 22 4 23 4 12 3 1 | 3 3 |
Kiểm tra: nhập
6 91 2 31 3 32 4 23 4 22 5 44 6 15 6 13 5 44 5 1Bài 5. Thành phố gặp nhau
Phần tiêu đề “Bài 5. Thành phố gặp nhau”Có n thành phố, m con đường hai chiều có độ dài (tính bằng giờ). An ở thành phố a, Bình ở thành phố b, hai bạn cùng xuất phát và muốn gặp nhau ở một thành phố nào đó. Thời gian gặp là thời gian của người đến muộn hơn. Dòng đầu có n, m, a, b (1 ≤ a, b ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w. In ra thời gian gặp nhau sớm nhất, hoặc -1.
Ví dụ:
| Nhập | In ra |
|---|---|
5 5 1 51 2 22 3 23 4 24 5 21 5 20 | 4 |
Kiểm tra: nhập
7 8 2 71 2 32 3 53 4 24 7 11 5 45 6 66 7 23 6 3Bài 6. Đường ít trạm nhất trong các đường ngắn nhất
Phần tiêu đề “Bài 6. Đường ít trạm nhất trong các đường ngắn nhất”Có n thành phố, m con đường hai chiều có độ dài. Dòng đầu có n, m (2 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w. Trong các đường đi ngắn nhất từ 1 tới n, chọn đường đi qua ít con đường nhất. In ra độ dài và số con đường của nó, hoặc -1 -1 nếu không tới được.
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 2 22 4 21 3 13 4 3 | 4 2 |
Kiểm tra: nhập
6 81 2 12 3 13 6 11 4 24 6 11 5 15 6 22 6 3Nhóm 2. Dijkstra trên trạng thái và biến thể
Phần tiêu đề “Nhóm 2. Dijkstra trên trạng thái và biến thể”Bài 7. Một phiếu giảm giá
Phần tiêu đề “Bài 7. Một phiếu giảm giá”Có n thành phố, m chuyến bay một chiều, chuyến thứ i bay từ u tới v giá w. Bạn có một phiếu giảm giá, dùng cho đúng một chuyến bay thì giá chuyến đó còn một nửa (làm tròn xuống). Dòng đầu có n, m (2 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 109). In ra tổng giá ít nhất để bay từ 1 tới n, hoặc -1.
Ví dụ:
| Nhập | In ra |
|---|---|
3 41 2 32 3 11 3 72 1 5 | 2 |
Kiểm tra: nhập
5 71 2 102 5 101 3 33 4 34 5 91 5 303 5 25Bài 8. Đường rộng nhất
Phần tiêu đề “Bài 8. Đường rộng nhất”Có n thành phố, m con đường hai chiều, đường thứ i có tải trọng tối đa c (xe nặng hơn c không đi qua được). Dòng đầu có n, m (2 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, c (1 ≤ c ≤ 109). In ra khối lượng lớn nhất của một chiếc xe đi được từ 1 tới n, hoặc 0 nếu không có đường.
Ví dụ:
| Nhập | In ra |
|---|---|
4 51 2 52 4 31 3 43 4 62 3 8 | 5 |
Kiểm tra: nhập
6 81 2 102 3 43 6 91 4 74 5 85 6 62 5 54 3 3Bài 9. Đường ngắn thứ hai
Phần tiêu đề “Bài 9. Đường ngắn thứ hai”Có n thành phố, m con đường hai chiều có độ dài. Dòng đầu có n, m (2 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 106). Đường đi được phép quay lại thành phố cũ và đi lại con đường cũ. In ra độ dài đường đi ngắn thứ hai từ 1 tới n: nhỏ nhất trong các độ dài lớn hơn hẳn độ dài ngắn nhất. Nếu không có, in -1.
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 2 1002 4 2002 3 2503 4 100 | 450 |
Kiểm tra: nhập
5 61 2 32 5 41 3 23 5 62 3 14 5 2Bài 10. Đường chi phí nhỏ nhất trên bảng
Phần tiêu đề “Bài 10. Đường chi phí nhỏ nhất trên bảng”Bảng h hàng, w cột, ô (r, c) có chi phí arc khi bước vào (ô xuất phát cũng tính). Đi từ ô trên cùng bên trái tới ô dưới cùng bên phải, mỗi bước sang ô chung cạnh (được đi cả bốn hướng). Dòng đầu có h, w (1 ≤ h, w ≤ 500), h dòng sau mỗi dòng có w số (0 ≤ arc ≤ 106). In ra tổng chi phí nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
3 31 9 11 9 11 1 1 | 5 |
Kiểm tra: nhập
4 51 3 1 2 97 3 4 9 91 7 5 5 32 3 4 2 5Nhóm 3. BFS 0-1
Phần tiêu đề “Nhóm 3. BFS 0-1”Bài 11. Phá ít tường nhất
Phần tiêu đề “Bài 11. Phá ít tường nhất”Mê cung h hàng, w cột gồm . và #. Đi từ ô trên cùng bên trái tới ô dưới cùng bên phải (cả hai là .), mỗi bước sang ô chung cạnh; bước vào ô # thì phải phá bức tường đó. Dòng đầu có h, w (1 ≤ h, w ≤ 1000), h dòng sau là mê cung. In ra số bức tường ít nhất phải phá.
Ví dụ:
| Nhập | In ra |
|---|---|
3 4.#..##.#.... | 1 |
Kiểm tra: nhập
5 6.##....#.##..#.#..##.#.#...#..Bài 12. Sửa mũi tên
Phần tiêu đề “Bài 12. Sửa mũi tên”Bảng h hàng, w cột, mỗi ô ghi một mũi tên R, L, D, U (phải, trái, xuống, lên). Đứng ở một ô thì đi theo mũi tên của ô đó sang ô kề miễn phí; muốn đi hướng khác thì phải sửa mũi tên của ô đó, tốn 1. Dòng đầu có h, w (1 ≤ h, w ≤ 500), h dòng sau mỗi dòng là w ký tự. In ra số lần sửa ít nhất để đi từ ô trên cùng bên trái tới ô dưới cùng bên phải.
Ví dụ:
| Nhập | In ra |
|---|---|
3 3RRDULDLLR | 0 |
Kiểm tra: nhập
4 4RRRRLLLLRRRRLLLLNhóm 4. Cạnh âm và mọi cặp đỉnh
Phần tiêu đề “Nhóm 4. Cạnh âm và mọi cặp đỉnh”Bài 13. Cạnh âm
Phần tiêu đề “Bài 13. Cạnh âm”Đồ thị có hướng n đỉnh, m cạnh, cạnh có thể có độ dài âm; đề bảo đảm không có chu trình âm. Dòng đầu có n, m (1 ≤ n ≤ 1000, 0 ≤ m ≤ 3000), m dòng sau mỗi dòng có u, v, w (|w| ≤ 106). In ra độ dài đường ngắn nhất từ đỉnh 1 tới các đỉnh 1, 2, …, n (INF nếu không tới được).
Ví dụ:
| Nhập | In ra |
|---|---|
4 51 2 41 3 23 2 -32 4 13 4 5 | 0 -1 2 0 |
Kiểm tra: nhập
6 81 2 61 3 72 4 53 4 -34 2 -22 5 -43 6 95 6 7Bài 14. Có chu trình âm không
Phần tiêu đề “Bài 14. Có chu trình âm không”Đồ thị có hướng n đỉnh, m cạnh, độ dài có thể âm. Dòng đầu có n, m (1 ≤ n ≤ 1000, 0 ≤ m ≤ 3000), m dòng sau mỗi dòng có u, v, w (|w| ≤ 106). In ra YES nếu đồ thị có một chu trình mà tổng độ dài các cạnh âm, ngược lại NO.
Ví dụ:
| Nhập | In ra |
|---|---|
3 31 2 12 3 -23 1 0 | YES |
Kiểm tra: nhập
5 61 2 32 3 -13 1 -14 5 25 4 -13 4 1Bài 15. Hỏi khoảng cách giữa hai thành phố
Phần tiêu đề “Bài 15. Hỏi khoảng cách giữa hai thành phố”Có n thành phố, m con đường hai chiều có độ dài, và q câu hỏi. Dòng đầu có n, m, q (1 ≤ n ≤ 300, 0 ≤ m ≤ n × n, 1 ≤ q ≤ 105), m dòng sau mỗi dòng có u, v, w (1 ≤ w ≤ 109), q dòng sau mỗi dòng có a, b. Với mỗi câu hỏi, in ra quãng đường ngắn nhất từ a tới b, hoặc -1.
Ví dụ:
| Nhập | In ra |
|---|---|
4 4 31 2 52 3 13 4 21 4 101 42 44 1 | 838 |
Kiểm tra: nhập
5 6 41 2 22 3 23 4 24 5 21 5 202 5 31 53 51 14 2Bài 16. Thành phố trung tâm
Phần tiêu đề “Bài 16. Thành phố trung tâm”Có n thành phố, m con đường hai chiều có độ dài, mọi thành phố đi được tới nhau. Chọn một thành phố làm trung tâm sao cho quãng đường từ trung tâm tới thành phố xa nhất là nhỏ nhất. Dòng đầu có n, m (1 ≤ n ≤ 300, n - 1 ≤ m ≤ n × n), m dòng sau mỗi dòng có u, v, w. In ra thành phố được chọn (nếu nhiều, số nhỏ nhất) và quãng đường tới thành phố xa nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 2 32 3 33 4 34 1 10 | 2 6 |
Kiểm tra: nhập
6 81 2 42 3 23 4 54 5 15 6 36 1 72 5 63 6 4