BFS 0-1 - Tìm Đường Đi Ngắn Nhất Với Trọng Số 0/1¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: VNOI Wiki, CP-Algorithms - 0-1 BFS
1. Bản chất vấn đề¶
Bài toán: Tìm đường đi ngắn nhất trên đồ thị trọng số 0/1¶
Cho đồ thị \(N\) đỉnh, \(M\) cạnh, mỗi cạnh có trọng số 0 hoặc 1. Tìm đường đi ngắn nhất từ đỉnh \(S\) đến tất cả các đỉnh.
Dijkstra: \(O(M \log N)\) — chấp nhận được.
BFS 0-1: \(O(N + M)\) — nhanh hơn! Không cần priority queue.
Khi nào dùng BFS 0-1?¶
| Tình huống | Mô tả |
|---|---|
| Trọng số cạnh chỉ là 0 hoặc 1 | Ví dụ: đảo bit, bật/tắt công tắc |
| Bài toán "chi phí đổi trạng thái" | Mỗi bước có chi phí 0 (giữ nguyên) hoặc 1 (đổi) |
| Grid với ô đi được / không đi được | Đi qua ô trống = 0, phá tường = 1 |
2. Tư duy cốt lõi¶
Ý tưởng: Deque thay vì Priority Queue¶
BFS thường dùng queue (tất cả cạnh trọng số 1). BFS 0-1 dùng deque:
- Cạnh trọng số 0: đẩy vào đầu deque (giống BFS thường — ưu tiên).
- Cạnh trọng số 1: đẩy vào cuối deque (chậm hơn 1 bước).
Điều này đảm bảo: đỉnh được xử lý theo thứ tự khoảng cách tăng dần (tương tự Dijkstra).
Trace chi tiết¶
Đồ thị: 5 đỉnh, cạnh có trọng số 0/1.
| Cạnh | Trọng số |
|---|---|
| \(0 \to 1\) | 0 |
| \(0 \to 2\) | 1 |
| \(1 \to 3\) | 1 |
| \(2 \to 3\) | 0 |
| \(3 \to 4\) | 0 |
Tìm đường ngắn nhất từ đỉnh 0:
| Bước | Deque (trái → phải) | Xử lý đỉnh | Cập nhật | dist[] |
|---|---|---|---|---|
| 0 | \([0]\) | 0 | \(dist[0] = 0\), thêm 1 vào đầu (w=0), thêm 2 vào cuối (w=1) | \([0, \infty, \infty, \infty, \infty]\) |
| 1 | \([1, 2]\) → pop 1 | 1 | \(dist[1] = 0\), thêm 3 vào cuối (w=1) | \([0, 0, \infty, \infty, \infty]\) |
| 2 | \([2, 3]\) → pop 2 | 2 | \(dist[2] = 1\), không thêm gì mới | \([0, 0, 1, \infty, \infty]\) |
| 3 | \([3]\) → pop 3 | 3 | \(dist[3] = 1\), thêm 4 vào đầu (w=0) | \([0, 0, 1, 1, \infty]\) |
| 4 | \([4]\) → pop 4 | 4 | \(dist[4] = 1\) | \([0, 0, 1, 1, 1]\) |
Kết quả: \(dist = [0, 0, 1, 1, 1]\)
Minh họa deque¶
flowchart LR
A["Deque: [0]"] --> B["Xử lý 0\nThêm 1(w=0) vào ĐẦU\nThêm 2(w=1) vào CUỐI"]
B --> C["Deque: [1, 2]"]
C --> D["Xử lý 1\nThêm 3(w=1) vào CUỐI"]
D --> E["Deque: [2, 3]"]
E --> F["Xử lý 2 (bỏ qua, đã thăm)"]
F --> G["Deque: [3]"]
G --> H["Xử lý 3\nThêm 4(w=0) vào ĐẦU"]
H --> I["Deque: [4]"]
3. Phân tích tính đúng đắn¶
Tại sao Deque đảm bảo đúng?¶
Bất biến: Khi xử lý đỉnh \(u\), \(dist[u]\) đã là khoảng cách ngắn nhất.
Chứng minh: Các đỉnh trong deque được sắp xếp theo khoảng cách không giảm:
- Cạnh w=0: đỉnh mới có cùng khoảng cách → đẩy vào đầu (trước các đỉnh có khoảng cách lớn hơn).
- Cạnh w=1: đỉnh mới có khoảng cách +1 → đẩy vào cuối (sau các đỉnh có khoảng cách hiện tại).
Tương tự Dijkstra với priority queue, nhưng khai thác trọng số 0/1 để dùng deque thay vì heap.
4. Đánh giá độ phức tạp¶
| Thuật toán | Thời gian | Không gian |
|---|---|---|
| Dijkstra | \(O(M \log N)\) | \(O(N + M)\) |
| BFS 0-1 | \(O(N + M)\) | \(O(N + M)\) |
| Bellman-Ford | \(O(NM)\) | \(O(N + M)\) |
Code minh họa¶
Ví dụ thực tế: Tìm đường trong mê cung có cửa khóa¶
Xét lưới \(3 \times 4\) với các ô trống (.) và tường (#). Đi qua ô trống tốn \(0\), phá tường tốn \(1\). Tìm chi phí nhỏ nhất từ \((0,0)\) đến \((2,3)\).
Mở rộng: Minh họa trên lưới
(0,0) xuống \((1,0)\) rồi xuống \((2,0)\) (chi phí \(0\)). Từ \((2,0)\) qua \((2,1)\) (chi phí \(0\)), phá tường ở \((1,1)\) (chi phí \(1\)). Tuy nhiên đường đi tối ưu là: \((0,0) \rightarrow (0,1)\) (0), \(\rightarrow\) phá tường \((0,2)\) (1), \(\rightarrow (0,3)\) (0). Tổng \(= 1\)! Cần chạy BFS 0-1 để kiểm tra chính xác.
Quy ước trên lưới:
- Ô trống .: trọng số \(0\) khi di chuyển từ ô lân cận sang
- Tường #: trọng số \(1\) khi đi qua (phá tường)
- 4 hướng di chuyển: lên, xuống, trái, phải
Cài đặt trên lưới:
5. Mẹo và lưu ý¶
5.1 Khi nào dùng BFS 0-1 thay vì Dijkstra?¶
| Tiêu chí | BFS 0-1 | Dijkstra |
|---|---|---|
| Trọng số chỉ có \(\{0, 1\}\) | ✅ Chọn BFS 0-1 | Dùng được nhưng chậm hơn |
| Trọng số có giá trị \(> 1\) | ❌ Không dùng được | ✅ Bắt buộc dùng |
| Có trọng số âm | ❌ Không dùng được | ❌ Không dùng được (cần Bellman-Ford) |
| Tốc độ | \(O(V+E)\) | \(O((V+E)\log V)\) |
| Cài đặt | Đơn giản (deque) | Cần priority queue |
5.2 Cạm bẫy thường gặp¶
- Quên đẩy vào đúng đầu/cuối: Cạnh \(w=0\) phải đẩy
push_front, cạnh \(w=1\) phải đẩypush_back. Nếu đảo ngược, thuật toán sẽ sai vì mất thứ tự ưu tiên khoảng cách. - Không kiểm tra
dist[u] + w < dist[v]: Một đỉnh có thể bị đẩy vào deque nhiều lần. Luôn kiểm tra điều kiện cập nhật trước khi đẩy vào deque để tránh vòng lặp vô hạn. - Nhầm với BFS thường: BFS thường dùng queue và chỉ hoạt động đúng khi mọi cạnh có trọng số bằng nhau. Với trọng số \(0/1\), BFS thường sẽ cho kết quả sai.
- Đồ thị không liên thông: Luôn khởi tạo
distvới giá trị vô cùng và in-1cho các đỉnh không đến được. - Trọng số cạnh âm: BFS 0-1 KHÔNG hoạt động với cạnh trọng số âm (kể cả \(-1\)). Thuật toán dựa trên tính chất khoảng cách không giảm của deque.
5.3 Mẹo tối ưu¶
- Dùng
dequetừ<deque>trong C++ hoặccollections.dequetrong Python. - Với bài toán trên lưới, mã hóa vị trí \((r,c)\) thành một số nguyên \(r \times C + c\) để dùng deque 1 chiều thay vì deque chứa
pair. - Khi cần truy vết đường đi, lưu thêm mảng
parent[v]và cập nhật mỗi khidist[v]được cập nhật. - Với đồ thị có hướng, bỏ dòng
adj[v].push_back({u, w})trong code C++ và Python ở trên.
6. Bài tập luyện tập trên FPTOJ¶
Dưới đây là danh sách các bài tập thực hành về BFS ~0\text{-}1~ trên hệ thống FPTOJ được thiết kế đồng bộ với bài học lý thuyết này:
| Mã bài | Tên bài tập | Độ khó | Kiểu bài tập (Bản chất) | Lời giải chi tiết (Editorial) |
|---|---|---|---|---|
bfs01-switch |
Công tắc cửa ngầm | ⭐⭐ | BFS ~0\text{-}1~ trên đồ thị có hướng | Xem hướng dẫn |
bfs01-gridwall |
Bình vượt mê cung | ⭐⭐ | BFS ~0\text{-}1~ trên lưới ~2D~ | Xem hướng dẫn |
bfs01-parity |
Tuyến đường chẵn lẻ | ⭐⭐ | BFS ~0\text{-}1~ trên đồ thị vô hướng | Xem hướng dẫn |
bfs01-toll |
Trạm thu phí xa lộ | ⭐⭐ | BFS ~0\text{-}1~ trên đồ thị vô hướng | Xem hướng dẫn |
bfs01-revedge |
Tý đảo hướng đường | ⭐⭐⭐ | BFS ~0\text{-}1~ đảo chiều cạnh đồ thị | Xem hướng dẫn |
bfs01-stepup |
Leo núi nhân tạo | ⭐⭐⭐ | BFS ~0\text{-}1~ trên lưới có cao độ | Xem hướng dẫn |
bfs01-teleport |
Mạng lưới dịch chuyển | ⭐⭐⭐ | BFS ~0\text{-}1~ trên trục lộ ~1D~ và portal | Xem hướng dẫn |
bfs01-traffic |
Ngã tư luồng ưu tiên | ⭐⭐⭐ | BFS ~0\text{-}1~ lưới với hướng ưu tiên | Xem hướng dẫn |
bfs01-turn |
Lái xe ít rẽ nhất | ⭐⭐⭐⭐ | BFS ~0\text{-}1~ lưới với 4 trạng thái hướng | Xem hướng dẫn |
bfs01-subway |
Đổi tàu điện ngầm | ⭐⭐⭐⭐ | BFS ~0\text{-}1~ đồ thị ảo phân cấp | Xem hướng dẫn |