Bài 40: SCC & Cầu & Khớp - Thành phần liên thông mạnh!¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: VNOI Wiki - Cây DFS, CP-Algorithms
Bạn sẽ học được gì?¶
- Thuật toán Tarjan tìm Thành phần liên thông mạnh (SCC) trong đồ thị có hướng
- Thuật toán Kosaraju - phương pháp thay thế để tìm SCC
- Xây dựng đồ thị co (condensation graph) từ các SCC
- Tìm cầu (bridges) trong đồ thị vô hướng
- Tìm khớp (articulation points) trong đồ thị vô hướng
- Ứng dụng thực tế: 2-edge-connected components, biconnected components
1. SCC - Thành phần liên thông mạnh là gì?¶
Ẩn dụ: Mạng đường một chiều¶
Bạn đang ở thành phố A, muốn đi đến thành phố B và quay về A. Nếu có đường đi cả hai chiều → A và B thuộc cùng một "vùng liên thông mạnh". Nếu chỉ đi được một chiều → không thuộc cùng SCC.
SCC (Strongly Connected Component) trong đồ thị có hướng là tập con lớn nhất các đỉnh sao cho từ bất kỳ đỉnh nào trong tập, ta đều đi được đến bất kỳ đỉnh nào còn lại.
graph LR
subgraph SCC1["SCC 1"]
A(("1")) --> B(("2"))
B --> C(("3"))
C --> A
end
subgraph SCC2["SCC 2"]
D(("4")) --> E(("5"))
E --> D
end
subgraph SCC3["SCC 3"]
F(("6"))
end
B --> D
C --> F
E --> F
Trong đồ thị trên: - SCC 1: {1, 2, 3} — đi được từ bất kỳ đỉnh nào đến bất kỳ đỉnh nào trong tập - SCC 2: {4, 5} — 4 → 5 → 4 - SCC 3: {6} — đỉnh cô lập (không có chu trình đi qua)
Tại sao SCC quan trọng?¶
- Nén đồ thị: Biến đồ thị có hướng phức tạp thành DAG (đồ thị không chu trình) — dễ xử lý hơn
- Tìm đường đi: Kiểm tra 2 đỉnh có thể đi lẫn nhau không
- Phân tích mạng: Xác định nhóm node phụ thuộc lẫn nhau
2. Thuật toán Tarjan tìm SCC¶
Ý tưởng cốt lõi¶
Dùng DFS, gán cho mỗi đỉnh 2 giá trị:
- num[u]: thứ tự DFS (thời điểm thăm u)
- low[u]: giá trị num nhỏ nhất mà từ u có thể đi tới được (qua cây DFS hoặc cạnh ngược)
Nếu low[u] == num[u] → u là gốc của một SCC. Tất cả đỉnh trong stack từ u trở lên tạo thành 1 SCC.
Các biến cần dùng¶
| Biến | Ý nghĩa |
|---|---|
num[u] |
Thứ tự DFS của đỉnh u (0 = chưa thăm) |
low[u] |
Giá trị num nhỏ nhất mà u chạm tới được |
stack |
Stack chứa các đỉnh đang trong quá trình tìm SCC |
onStack[u] |
Đỉnh u có đang nằm trong stack không |
cnt |
Bộ đếm thứ tự DFS |
Minh họa từng bước¶
Cho đồ thị có 6 đỉnh:
graph LR
n1(("1")) --> n2(("2"))
n2 --> n3(("3"))
n3 --> n1
n3 --> n4(("4"))
n4 --> n5(("5"))
n5 --> n6(("6"))
n6 --> n4
Kết quả: 2 SCC → {1, 2, 3}, {4, 5, 6}.

Code hoàn chỉnh¶
Độ phức tạp¶
- Thời gian: O(V + E) — mỗi đỉnh và cạnh thăm đúng 1 lần
- Bộ nhớ: O(V) — stack và mảng num[], low[]
3. Thuật toán Kosaraju — Phương pháp 2 lượt DFS¶
Ý tưởng¶
- Lượt 1: DFS trên đồ thị gốc, ghi lại thứ tự hoàn thành (thêm đỉnh vào stack khi DFS xong)
- Lượt 2: Đảo ngược tất cả cạnh. Duyệt DFS theo thứ tự từ stack (đỉnh hoàn thành muộn nhất trước). Mỗi DFS tạo thành 1 SCC.
Tại sao hoạt động?¶
Đỉnh hoàn thành muộn nhất trong DFS lượt 1 thuộc SCC "gốc" (không bị SCC khác "hút"). Khi đảo cạnh, DFS từ đỉnh này chỉ thăm được đúng các đỉnh trong cùng SCC.
Code hoàn chỉnh¶
So sánh Tarjan vs Kosaraju¶
| Tarjan | Kosaraju | |
|---|---|---|
| Số lượt DFS | 1 lượt | 2 lượt |
| Đồ thị đảo | Không cần | Cần xây dựng |
| Bộ nhớ thêm | Stack | Stack + đồ thị đảo |
| Code | Phức tạp hơn | Đơn giản hơn |
| Độ phức tạp | O(V + E) | O(V + E) |
→ Tarjan phổ biến hơn trong thi đấu vì chỉ cần 1 DFS và không cần xây đồ thị đảo.
4. Đồ thị co (Condensation Graph)¶
Định nghĩa¶
Nén mỗi SCC thành 1 đỉnh "siêu". Kết quả là DAG (đồ thị có hướng không chu trình).
graph LR
subgraph SCC1["SCC {1,2,3}"]
direction LR
A(("1")) --> B(("2"))
B --> C(("3"))
C --> A
end
subgraph SCC2["SCC {4,5}"]
direction LR
D(("4")) --> E(("5"))
E --> D
end
F(("6"))
B --> D
C --> F
E --> F
Sau khi nén:
graph LR
S1(("SCC1")) --> S2(("SCC2"))
S1 --> S3(("SCC3"))
S2 --> S3
Tính chất quan trọng¶
- Luôn là DAG — không thể có chu trình vì nếu có, các SCC sẽ gộp lại thành 1
- Sắp xếp tô-pô — SCC nào hoàn thành DFS muộn nhất sẽ đứng đầu topological order
- Ứng dụng: DP trên DAG, tìm đường đi trong đồ thị có hướng
Xây dựng đồ thị co¶
5. Cầu (Bridges) trong đồ thị vô hướng¶
Ẩn dụ: Cây cầu huyết mạch¶
Một hòn đảo nối với đất liền bằng duy nhất một cây cầu. Nếu cầu sập → đảo bị cô lập! Cầu là cạnh mà khi xóa nó, số thành phần liên thông tăng lên.
Định nghĩa¶
Cầu (Bridge) là cạnh (u, v) mà sau khi xóa, đỉnh u và v không còn liên thông với nhau.
graph LR
A(("1")) --- B(("2"))
B --- C(("3"))
C --- A
B --- D(("4"))
D --- E(("5"))
E --- F(("6"))
F --- D
Trong đồ thị trên: - Cạnh (B, D) = cầu — xóa nó thì {1,2,3} tách rời {4,5,6} - Các cạnh trong {1,2,3} và {4,5,6} không phải cầu vì vẫn có đường đi khác
Thuật toán¶
Dùng DFS tree + mảng low[]:
Cạnh (u, v) là cầu khi và chỉ khi v là con của u trong cây DFS và low[v] > num[u].
Nghĩa là: từ v (và các con của v), không có đường nào quay lại u hoặc tổ tiên của u → xóa (u, v) sẽ tách v ra khỏi u.
Code hoàn chỉnh¶
Điều kiện nhận cầu¶
| Điều kiện | Ý nghĩa |
|---|---|
low[v] > num[u] |
Là cầu — từ v không có đường quay lại u hay tổ tiên u |
low[v] == num[u] |
Không phải cầu — v có đường quay lại u |
low[v] < num[u] |
Không phải cầu — v có đường quay lại tổ tiên của u |
6. Khớp (Articulation Points) trong đồ thị vô hướng¶
Ẩn dụ: Trung tâm giao thông¶
Thành phố có nhiều ngã tư. Nếu một ngã tư bị phong tỏa mà giao thông bị chia cắt → ngã tư đó là khớp (đỉnh trụ).
Định nghĩa¶
Khớp (Articulation Point / Cut Vertex) là đỉnh mà khi xóa nó (và các cạnh liên quan), số thành phần liên thông tăng lên.
Hai trường hợp¶
Trường hợp 1: Đỉnh gốc của cây DFS
Nếu gốc có ≥ 2 con trong cây DFS → là khớp. Vì 2 nhánh con không có đường nào nối nhau ngoài qua gốc.
Trường hợp 2: Đỉnh không phải gốc
Đỉnh u là khớp khi tồn tại con v trong cây DFS sao cho low[v] >= num[u]. Nghĩa là từ v không có đường nào quay về tổ tiên của u → xóa u sẽ tách v ra.
graph LR
A(("1")) --- B(("2"))
B --- C(("3"))
C --- A
B --- D(("4"))
D --- E(("5"))
- Đỉnh 2 là khớp: xóa 2 → {1,3} tách khỏi {4,5}
- Đỉnh 4 là khớp: xóa 4 → {5} bị cô lập
- Đỉnh 1, 3, 5 không phải khớp
Code hoàn chỉnh¶
So sánh Cầu vs Khớp¶
| Cầu (Bridge) | Khớp (Articulation Point) | |
|---|---|---|
| Là gì? | Cạnh | Đỉnh |
| Điều kiện | low[v] > num[u] |
low[v] >= num[u] (hoặc gốc ≥ 2 con) |
| Xóa → | Tách 2 nhóm đỉnh | Tách 2+ nhóm đỉnh |
| Cạnh/đỉnh đặc biệt | Cạnh cầu luôn nối 2 SCC | Khớp có thể thuộc nhiều khối |
7. Ứng dụng¶
7.1. 2-Edge-Connected Components (2ECC)¶
Tập đỉnh mà giữa bất kỳ 2 đỉnh nào, tồn tại ≥ 2 đường đi cạnh-disjoint (không chia sẻ cạnh).
→ Tương đương: trong 2ECC, không có cầu nào.
Cách tìm: Xóa tất cả cầu, mỗi thành phần liên thông còn lại là 1 block 2ECC.
7.2. 2-Vertex-Connected Components (Biconnected Components)¶
Tập đỉnh mà giữa bất kỳ 2 đỉnh nào, tồn tại ≥ 2 đường đi vertex-disjoint (không chia sẻ đỉnh ngoài 2 đỉnh đầu).
→ Tương đương: trong biconnected component, không có khớp nào.
Cách tìm: Dùng DFS + stack cạnh. Khi phát hiện khớp, pop stack cho đến khi lấy được tất cả cạnh trong block.
7.3. Tìm cầu trong mạng¶
Ứng dụng thực tế: xác định kết nối quan trọng trong mạng máy tính. Nếu một đường truyền (cầu) bị đứt → mạng bị chia cắt.
7.4. Orient edges to make graph strongly connected¶
Cho đồ thị vô hướng liên thông. Có thể hướng hóa các cạnh sao cho đồ thị có hướng trở thành liên thông mạnh khi và chỉ khi đồ thị không có cầu.
8. Lưu ý / Cạm bẫy hay gặp¶
Bẫy 1: Quên khởi tạo low[u] = num[u]¶
Bẫy 2: Không xử lý đồ thị không liên thông¶
Bẫy 3: Nhầm directed vs undirected SCC¶
- Đồ thị có hướng: SCC là tập đỉnh đi được lẫn nhau (dùng Tarjan/Kosaraju)
- Đồ thị vô hướng: Khái niệm SCC không áp dụng (mọi đỉnh trong thành phần liên thông đều đi được lẫn nhau khi xem như đồ thị có hướng). Dùng connected component thay vì Tarjan!
Bẫy 4: Cạnh trùng trong đồ thị vô hướng¶
Bẫy 5: low[v] >= num[u] vs low[v] > num[u]¶
low[v] > num[u]→ cầu (cạnh)low[v] >= num[u]→ khớp (đỉnh, với u không phải gốc)
Bẫy 6: Gốc DFS có 1 con nhưng vẫn xét là khớp¶
Mẹo thi cử¶
| Bài toán | Thuật toán |
|---|---|
| Tìm SCC | Tarjan hoặc Kosaraju |
| Đồ thị co (DAG từ SCC) | Tarjan + xây condensation |
| Tìm cầu | DFS + low[v] > num[u] |
| Tìm khớp | DFS + low[v] >= num[u] hoặc gốc ≥ 2 con |
| 2-edge-connected components | Xóa cầu → DFS/BFS |
| Biconnected components | DFS + stack cạnh |
9. Bài tập luyện tập¶
| Bài | Nền tảng | Độ khó | Chủ đề |
|---|---|---|---|
| CSES - Planets and Kingdoms | CSES | ⭐⭐ | SCC cơ bản |
| CSES - Coin Collector | CSES | ⭐⭐⭐ | SCC + DP trên DAG |
| CSES - Road Construction | CSES | ⭐⭐ | Bridges |
| CF 118E - Bertown roads | CF | ⭐⭐⭐ | Bridges + hướng hóa cạnh |
| VNOJ - NKPOLICE | VNOJ | ⭐⭐⭐ | Articulation points |
| CSES - Giant Pizza | CSES | ⭐⭐⭐ | 2-SAT + SCC |
| SPOJ - BOTTOM | SPOJ | ⭐⭐ | SCC (tìm sink components) |
| VNOJ - QTGRAPH | VNOJ | ⭐⭐ | SCC + topo sort |
| CF 999E - Reachability from the Capital | CF | ⭐⭐⭐ | SCC + greedy |
| CSES - New Flight Routes | CSES | ⭐⭐⭐ | SCC + nối đỉnh |
Bài viết liên quan¶
Tài liệu tham khảo¶
- CP-Algorithms - Finding Bridges
- CP-Algorithms - Finding Articulation Points
- CP-Algorithms - Strongly Connected Components (Tarjan)
- CP-Algorithms - Finding Bridges Online
- VNOI Wiki - Cây DFS và ứng dụng
- VNOI Wiki - Bridge & Articulation Point
- USACO Guide - BCCs
- YouTube - Bridges and Articulation Points (Tushar Roy)
Bài tiếp theo: Bài 41: Network Flow