Disjoint Set Union (DSU)
Có n người, lần lượt có tin “người u và người v là bạn”. Bạn của bạn cũng tính là cùng nhóm. Sau mỗi tin, hỏi “x và y có cùng nhóm không?”. Mỗi lần hỏi chạy BFS từ x thì mất tới n bước. DSU (Disjoint Set Union, còn gọi là Union-Find) trả lời mỗi câu hỏi và gộp mỗi nhóm gần như tức thì.
Ý tưởng: mỗi nhóm có một “trưởng nhóm”
Phần tiêu đề “Ý tưởng: mỗi nhóm có một “trưởng nhóm””Mỗi nhóm được biểu diễn bằng một cây: mỗi người lưu cha[x], người đứng đầu (gốc cây) có
cha[x] = x và là trưởng nhóm.
- Tìm trưởng nhóm của x: đi theo
chacho đến khi gặp gốc. - Hai người cùng nhóm khi có cùng trưởng nhóm.
- Gộp hai nhóm: tìm hai trưởng nhóm, cho trưởng nhóm này làm cha của trưởng nhóm kia.
Bốn người 1 2 3 4, lần lượt gộp (1, 2), (3, 4), (2, 4):
| Thao tác | cha[1] | cha[2] | cha[3] | cha[4] | Các nhóm |
|---|---|---|---|---|---|
| ban đầu | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 |
| gộp 1, 2 | 1 | 1 | 3 | 4 | 1 2 | 3 | 4 |
| gộp 3, 4 | 1 | 1 | 3 | 3 | 1 2 | 3 4 |
| gộp 2, 4 | 1 | 1 | 1 | 3 | 1 2 3 4 |
Ở bước cuối, trưởng nhóm của 2 là 1, của 4 là 3, nên cho cha[3] = 1.
Hai cải tiến làm DSU rất nhanh
Phần tiêu đề “Hai cải tiến làm DSU rất nhanh”Nếu gộp tùy tiện, cây có thể thành một đường dài n, tìm trưởng nhóm mất n bước. Hai cải tiến:
- Gộp theo kích thước: luôn gắn gốc của nhóm nhỏ hơn vào gốc của nhóm lớn hơn. Mỗi lần một người bị “chuyển xuống” một tầng thì nhóm của người đó ít nhất to gấp đôi, nên cây không cao quá log2n tầng.
- Nén đường đi: khi tìm trưởng nhóm của x, cho mọi người trên đường đi trỏ thẳng vào gốc. Lần sau tìm lại chỉ mất một bước.
Dùng cả hai thì mỗi thao tác trung bình chỉ mất vài bước (về lý thuyết là một hàm tăng cực kỳ chậm, với mọi n trong đề thi đều không quá 5).
import sys
def tim(x): goc = x while cha[goc] != goc: goc = cha[goc] while cha[x] != goc: # nén đường đi cha[x], x = goc, cha[x] return goc
def gop(u, v): u, v = tim(u), tim(v) if u == v: return False # đã cùng nhóm if kich_thuoc[u] < kich_thuoc[v]: u, v = v, u cha[v] = u # gắn nhóm nhỏ vào nhóm lớn kich_thuoc[u] += kich_thuoc[v] return True
du_lieu = sys.stdin.read().split()n, q = int(du_lieu[0]), int(du_lieu[1])cha = list(range(n + 1))kich_thuoc = [1] * (n + 1)ket_qua = []for t in range(q): loai, u, v = du_lieu[2 + 3 * t], int(du_lieu[3 + 3 * t]), int(du_lieu[4 + 3 * t]) if loai == '1': gop(u, v) else: ket_qua.append("YES" if tim(u) == tim(v) else "NO")print("\n".join(ket_qua))#include <iostream>#include <vector>#include <numeric>using namespace std;
vector<int> cha, kichThuoc;
int tim(int x) { if (cha[x] == x) return x; return cha[x] = tim(cha[x]); // nén đường đi}
bool gop(int u, int v) { u = tim(u), v = tim(v); if (u == v) return false; // đã cùng nhóm if (kichThuoc[u] < kichThuoc[v]) swap(u, v); cha[v] = u; // gắn nhóm nhỏ vào nhóm lớn kichThuoc[u] += kichThuoc[v]; return true;}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, q; cin >> n >> q; cha.resize(n + 1); iota(cha.begin(), cha.end(), 0); kichThuoc.assign(n + 1, 1); while (q--) { int loai, u, v; cin >> loai >> u >> v; if (loai == 1) gop(u, v); else cout << (tim(u) == tim(v) ? "YES" : "NO") << "\n"; } return 0;}Lưu ý: trong Python, hàm tim viết bằng vòng lặp thay vì đệ quy, vì cây có thể sâu tới hàng nghìn
tầng trước khi được nén, đệ quy dễ vượt giới hạn. Trong C++ đệ quy vẫn ổn nhờ gộp theo kích thước giữ
cây thấp.
DSU dùng được cho những gì?
Phần tiêu đề “DSU dùng được cho những gì?”| Câu hỏi | Cách dùng |
|---|---|
| số nhóm (thành phần liên thông) | bắt đầu với n nhóm, mỗi lần gop thành công thì bớt 1 |
| cạnh nào tạo chu trình | cạnh (u, v) mà u, v đã cùng nhóm |
| kích thước nhóm của x | kich_thuoc[tim(x)] |
| xóa dần cạnh | DSU chỉ gộp được, không tách được: đi ngược thời gian, coi xóa là thêm |
| cây khung nhỏ nhất | thuật toán Kruskal |
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 20 bài, chia thành 6 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. Gộp nhóm và đếm
Phần tiêu đề “Nhóm 1. Gộp nhóm và đếm”Bài 1. Số thành phần liên thông
Phần tiêu đề “Bài 1. Số thành phần liên thông”Có n thành phố và m con đường hai chiều. Dòng đầu có n, m (1 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v (có thể u = v hoặc có đường lặp lại). In ra số nhóm thành phố, hai thành phố cùng nhóm khi đi được tới nhau.
Ví dụ:
| Nhập | In ra |
|---|---|
6 41 22 34 53 1 | 3 |
Kiểm tra: nhập
10 71 55 92 23 44 88 310 6Bài 2. Có cùng nhóm không
Phần tiêu đề “Bài 2. Có cùng nhóm không”Dòng đầu có n, q (1 ≤ n, q ≤ 2 × 105), q dòng sau mỗi dòng là một thao tác 1 u v (u và v thành bạn) hoặc 2 u v (hỏi u và v có cùng nhóm bạn không). Với mỗi thao tác loại 2, in ra YES hoặc NO.
Ví dụ:
| Nhập | In ra |
|---|---|
5 61 1 22 1 31 2 32 1 31 4 52 3 5 | NOYESNO |
Kiểm tra: nhập
8 81 1 21 3 42 2 41 2 32 1 41 6 72 7 82 6 6Bài 3. Nhóm lớn nhất sau mỗi lần kết bạn
Phần tiêu đề “Bài 3. Nhóm lớn nhất sau mỗi lần kết bạn”Dòng đầu có n, m (1 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v: u và v thành bạn. Sau mỗi lần kết bạn, in ra (trên một dòng, cách nhau dấu cách) số người của nhóm đông nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
6 41 23 42 35 6 | 2 2 4 4 |
Kiểm tra: nhập
8 61 23 45 67 81 82 7Bài 4. Con đường đầu tiên tạo vòng
Phần tiêu đề “Bài 4. Con đường đầu tiên tạo vòng”Dòng đầu có n, m (1 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v (u khác v): lần lượt xây đường hai chiều nối u và v. In ra số thứ tự (từ 1) của con đường đầu tiên mà sau khi xây, mạng đường có một vòng (đi từ một thành phố qua các con đường khác nhau rồi quay về), hoặc 0 nếu không bao giờ có vòng.
Ví dụ:
| Nhập | In ra |
|---|---|
5 51 22 34 53 11 4 | 4 |
Kiểm tra: nhập
8 71 23 45 62 36 78 54 1Bài 5. Đường thừa
Phần tiêu đề “Bài 5. Đường thừa”Có n thành phố và m con đường hai chiều (có thể có đường nối một thành phố với chính nó, hoặc nhiều đường nối cùng hai thành phố). Dòng đầu có n, m (1 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v. In ra số đường nhiều nhất có thể phá bỏ mà hai thành phố nào trước đi được tới nhau thì vẫn đi được.
Ví dụ:
| Nhập | In ra |
|---|---|
5 61 22 33 14 54 52 2 | 3 |
Kiểm tra: nhập
10 121 22 33 44 11 35 66 77 58 89 1010 99 10Bài 6. Đếm cặp liên lạc được
Phần tiêu đề “Bài 6. Đếm cặp liên lạc được”Dòng đầu có n, m (1 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v: hai máy tính u, v nối với nhau. In ra số cặp máy (không kể thứ tự, hai máy khác nhau) liên lạc được với nhau qua các đường nối.
Ví dụ:
| Nhập | In ra |
|---|---|
6 41 22 34 56 6 | 4 |
Kiểm tra: nhập
10 61 23 44 55 37 89 7Nhóm 2. DSU trên bảng
Phần tiêu đề “Nhóm 2. DSU trên bảng”Bài 7. Đếm đảo
Phần tiêu đề “Bài 7. Đếm đảo”Bản đồ là bảng m hàng, n cột gồm # (đất) và . (nước). Hai ô đất chung cạnh thuộc cùng một đảo. Dòng đầu có m, n (1 ≤ m, n ≤ 1000), m dòng sau mỗi dòng là một xâu n ký tự. In ra số đảo.
Ví dụ:
| Nhập | In ra |
|---|---|
4 5##..##...#..#......# | 4 |
Kiểm tra: nhập
6 7#.#.#.####.###.......########.....##.###.#Bài 8. Đảo mọc lên
Phần tiêu đề “Bài 8. Đảo mọc lên”Biển là bảng m hàng, n cột, ban đầu toàn nước. Lần lượt có k lần một ô (r, c) nổi lên thành đất (một ô có thể nổi lên nhiều lần, lần sau không thay đổi gì). Dòng đầu có m, n, k (1 ≤ m, n ≤ 1000, 1 ≤ k ≤ 105), k dòng sau mỗi dòng có r, c. Sau mỗi lần, in ra số đảo (cách nhau dấu cách).
Ví dụ:
| Nhập | In ra |
|---|---|
3 3 51 11 31 23 32 3 | 1 2 1 2 1 |
Kiểm tra: nhập
4 4 71 12 21 24 43 32 33 4Bài 9. Bạn của bạn
Phần tiêu đề “Bài 9. Bạn của bạn”Dòng đầu có n, m, q (1 ≤ n ≤ 105, 0 ≤ m ≤ 2 × 105, 1 ≤ q ≤ 105), m dòng sau mỗi dòng có u, v: u và v là bạn; q dòng sau mỗi dòng có một người x. Với mỗi x, in ra số người khác x cùng nhóm bạn với x (bạn của bạn cũng tính).
Ví dụ:
| Nhập | In ra |
|---|---|
6 3 31 22 35 6146 | 201 |
Kiểm tra: nhập
10 6 41 23 44 52 57 88 917105Nhóm 3. Sắp xếp rồi gộp, đi ngược thời gian
Phần tiêu đề “Nhóm 3. Sắp xếp rồi gộp, đi ngược thời gian”Bài 10. Ngày đầu tiên thông suốt
Phần tiêu đề “Bài 10. Ngày đầu tiên thông suốt”Có n thành phố, chưa có đường nào. Lần lượt mỗi ngày xây một con đường hai chiều. Dòng đầu có n, m (2 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), dòng thứ i trong m dòng sau có u, v: đường xây ngày thứ i. In ra ngày đầu tiên mà từ mọi thành phố đi được tới mọi thành phố khác, hoặc -1 nếu đến hết m ngày vẫn chưa.
Ví dụ:
| Nhập | In ra |
|---|---|
4 51 23 41 22 31 4 | 4 |
Kiểm tra: nhập
6 71 23 45 61 32 44 51 6Bài 11. Thời điểm cả lớp quen nhau
Phần tiêu đề “Bài 11. Thời điểm cả lớp quen nhau”Lớp có n bạn. Có m sự kiện, sự kiện thứ i xảy ra lúc ti: hai bạn ui, vi làm quen. Các sự kiện được cho không theo thứ tự thời gian. Bạn của bạn coi là quen. Dòng đầu có n, m (2 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có t, u, v (1 ≤ t ≤ 109). In ra thời điểm sớm nhất mà cả lớp quen nhau, hoặc -1.
Ví dụ:
| Nhập | In ra |
|---|---|
4 420 1 25 3 412 2 330 1 4 | 20 |
Kiểm tra: nhập
6 650 1 210 3 470 5 620 2 390 4 540 1 6Bài 12. Phá đường
Phần tiêu đề “Bài 12. Phá đường”Có n thành phố và m con đường. Lần lượt phá từng con đường theo một thứ tự cho trước cho đến khi hết. Dòng đầu có n, m (1 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v (con đường thứ i), dòng cuối có m số: thứ tự phá (một hoán vị của 1, …, m). Sau mỗi lần phá, in ra số nhóm thành phố đi được tới nhau (cách nhau dấu cách).
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 22 33 44 12 4 1 3 | 1 2 3 4 |
Kiểm tra: nhập
6 61 22 33 14 55 61 46 3 1 4 2 5Bài 13. Đếm cặp đi được với tải trọng giới hạn
Phần tiêu đề “Bài 13. Đếm cặp đi được với tải trọng giới hạn”Có n thành phố và m con đường, đường thứ i nối ui, vi và có độ dốc wi. Một chiếc xe chỉ leo được dốc không quá x, nên chỉ đi được trên các đường có w ≤ x. Dòng đầu có n, m, q (1 ≤ n ≤ 105, 0 ≤ m, 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ó x. Với mỗi x, in ra số cặp thành phố (không kể thứ tự) đi được tới nhau chỉ dùng các đường có w ≤ x.
Ví dụ:
| Nhập | In ra |
|---|---|
5 4 31 2 32 3 74 5 23 4 52510 | 1410 |
Kiểm tra: nhập
7 6 41 2 102 3 204 5 55 6 156 7 253 4 304152530Nhóm 4. Ràng buộc giữa các phần tử
Phần tiêu đề “Nhóm 4. Ràng buộc giữa các phần tử”Bài 14. Bằng nhau và khác nhau
Phần tiêu đề “Bài 14. Bằng nhau và khác nhau”Có n biến x1, …, xn và m ràng buộc, mỗi ràng buộc là u = v hoặc u != v (xu bằng hoặc khác xv). Dòng đầu có n, m (1 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, ký hiệu (= hoặc !=), v. In ra YES nếu gán được giá trị cho các biến thỏa mọi ràng buộc, ngược lại NO.
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 = 22 = 33 != 41 != 3 | NO |
Kiểm tra: nhập
6 61 = 23 = 45 != 62 != 36 = 14 = 5Bài 15. Chia hai đội
Phần tiêu đề “Bài 15. Chia hai đội”Có n bạn cần chia thành hai đội. Lần lượt có m yêu cầu, mỗi yêu cầu là u v: u và v phải ở khác đội. Dòng đầu có n, m (1 ≤ n ≤ 105, 1 ≤ m ≤ 2 × 105), m dòng sau mỗi dòng có u, v. In ra số thứ tự (từ 1) của yêu cầu đầu tiên làm cho không thể chia đội thỏa mọi yêu cầu đã có, hoặc 0 nếu luôn chia được.
Ví dụ:
| Nhập | In ra |
|---|---|
4 41 22 33 41 3 | 4 |
Kiểm tra: nhập
6 61 23 45 62 34 56 1Bài 16. Chênh lệch cân nặng
Phần tiêu đề “Bài 16. Chênh lệch cân nặng”Có n hộp. Có m thông tin, mỗi thông tin u v d nghĩa là hộp v nặng hơn hộp u đúng d (d có thể âm); các thông tin không mâu thuẫn nhau. Sau đó có q câu hỏi u v. Dòng đầu có n, m, q (1 ≤ n ≤ 105, 0 ≤ m, q ≤ 105), |d| ≤ 106. Với mỗi câu hỏi, in ra (cân nặng hộp v) - (cân nặng hộp u) nếu suy ra được, ngược lại in ra ?.
Ví dụ:
| Nhập | In ra |
|---|---|
4 2 31 2 52 3 -21 33 11 4 | 3-3? |
Kiểm tra: nhập
6 4 41 2 103 2 44 5 -76 4 11 35 61 52 2Nhóm 5. DSU tìm chỗ trống
Phần tiêu đề “Nhóm 5. DSU tìm chỗ trống”Bài 17. Bãi đỗ xe
Phần tiêu đề “Bài 17. Bãi đỗ xe”Bãi đỗ xe có n chỗ đánh số 1 đến n xếp thành vòng tròn. Lần lượt có n xe vào, xe thứ i muốn đỗ chỗ pi; nếu chỗ đó đã có xe thì đi tiếp sang chỗ pi + 1, pi + 2, … (sau chỗ n là chỗ 1) và đỗ ở chỗ trống đầu tiên. Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số pi. In ra chỗ đỗ của từng xe.
Ví dụ:
| Nhập | In ra |
|---|---|
52 2 5 5 2 | 2 3 5 1 4 |
Kiểm tra: nhập
83 3 3 8 8 1 2 3Bài 18. Sơn tường
Phần tiêu đề “Bài 18. Sơn tường”Bức tường có n ô, ban đầu chưa sơn. Lần lượt có m lần sơn, lần thứ i sơn mọi ô từ li đến ri bằng màu ci (sơn đè lên màu cũ). Dòng đầu có n, m (1 ≤ n, m ≤ 2 × 105), m dòng sau mỗi dòng có l, r, c (1 ≤ c ≤ 109). In ra màu cuối cùng của từng ô, ô chưa sơn in 0.
Ví dụ:
| Nhập | In ra |
|---|---|
8 31 5 23 7 92 3 4 | 2 4 4 9 9 9 9 0 |
Kiểm tra: nhập
10 41 10 14 6 22 8 35 5 4Nhóm 6. Gộp thêm thông tin
Phần tiêu đề “Nhóm 6. Gộp thêm thông tin”Bài 19. Số màu trong nhóm
Phần tiêu đề “Bài 19. Số màu trong nhóm”Có n người, người thứ i mặc áo màu ci. Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số ci (1 ≤ ci ≤ 109), q dòng sau mỗi dòng là 1 u v (u và v vào cùng nhóm) hoặc 2 x (hỏi nhóm của x có bao nhiêu màu áo khác nhau). Với mỗi câu hỏi loại 2, in ra đáp số.
Ví dụ:
| Nhập | In ra |
|---|---|
5 61 2 1 3 22 11 1 21 3 12 31 4 52 5 | 122 |
Kiểm tra: nhập
8 85 5 7 9 7 5 2 91 1 22 21 3 41 2 32 11 6 71 8 62 7Bài 20. Nước dâng
Phần tiêu đề “Bài 20. Nước dâng”Bản đồ là bảng m hàng, n cột, ô (r, c) có độ cao h. Khi mực nước là x, các ô có độ cao lớn hơn x còn khô; hai ô khô chung cạnh thuộc cùng một vùng. Dòng đầu có m, n, q (1 ≤ m, n ≤ 500, 1 ≤ q ≤ 105), m dòng sau mỗi dòng có n số h (0 ≤ h ≤ 109), q dòng sau mỗi dòng có một mực nước x. Với mỗi x, in ra số vùng khô.
Ví dụ:
| Nhập | In ra |
|---|---|
3 3 31 5 24 3 62 7 1024 | 113 |
Kiểm tra: nhập
4 4 45 1 5 11 5 1 59 9 2 83 7 6 40147