Bài 19: Tổ Hợp & Xác Suất¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: VNOI Wiki - Cách tính số tổ hợp, Xác suất
1. Tổ hợp \(C(n, k)\)¶
Bản chất vấn đề¶
Cho \(n\) phần tử, chọn ra \(k\) phần tử mà không quan tâm thứ tự. Hỏi có bao nhiêu cách chọn?
Công thức:
Ví dụ: \(C(5,2) = \frac{5!}{2! \cdot 3!} = 10\).
Tư duy cốt lõi¶
Công thức truy hồi quan trọng nhất — định lý Pascal:
Giải thích: Xét phần tử thứ \(n\) trong tập \(n\) phần tử:
- Chọn phần tử thứ \(n\): Cần chọn thêm \(k-1\) phần tử từ \(n-1\) phần tử còn lại → \(C(n-1, k-1)\)
- Không chọn phần tử thứ \(n\): Cần chọn \(k\) phần tử từ \(n-1\) phần tử còn lại → \(C(n-1, k)\)
Tổng hai trường hợp chính là \(C(n, k)\).
Các tính chất cơ bản:
| Tính chất | Công thức |
|---|---|
| Đường biên | \(C(n, 0) = C(n, n) = 1\) |
| Đối xứng | \(C(n, k) = C(n, n-k)\) |
| Truy hồi | \(C(n, k) = C(n-1, k-1) + C(n-1, k)\) |
Tam giác Pascal¶
Bảng giá trị \(C(n, k)\):
| \(n\) \ \(k\) | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | 1 | |||||
| 1 | 1 | 1 | ||||
| 2 | 1 | 2 | 1 | |||
| 3 | 1 | 3 | 3 | 1 | ||
| 4 | 1 | 4 | 6 | 4 | 1 | |
| 5 | 1 | 5 | 10 | 10 | 5 | 1 |
Kiểm tra: \(C(4,2) = C(3,1) + C(3,2) = 3 + 3 = 6\) ✓
Phân tích tính đúng đắn¶
Định lý Pascal đúng vì mọi tập con \(k\) phần tử của \(n\) phần tử được phân thành hai nhóm không giao nhau:
- Nhóm chứa phần tử thứ \(n\): tương ứng \(C(n-1, k-1)\) cách
- Nhóm không chứa phần tử thứ \(n\): tương ứng \(C(n-1, k)\) cách
Hai nhóm này là phân hoạch đầy đủ → tổng bằng \(C(n, k)\).
Đánh giá độ phức tạp¶
Xây dựng tam giác Pascal đến hàng \(N\):
- Thời gian: \(O(N^2)\) — duyệt qua mọi cặp \((i, j)\) với \(0 \le j \le i \le N\)
- Bộ nhớ: \(O(N^2)\) — lưu toàn bộ bảng \(C\)
- Truy vấn: \(O(1)\) — chỉ cần tra cứu \(C[n][k]\)
2. Cách tính \(C(n, k)\) modulo \(10^9 + 7\)¶
Bản chất vấn đề¶
Với \(n\) lớn, \(n!\) vượt quá khả năng lưu trữ. Cần tính \(C(n, k) \bmod M\) với \(M = 10^9 + 7\) là số nguyên tố.
Hai phương pháp chính:
| Tam giác Pascal | Factorial + Inverse | |
|---|---|---|
| Preprocess | \(O(N^2)\) | \(O(N)\) |
| Truy vấn | \(O(1)\) | \(O(1)\) |
| Bộ nhớ | \(O(N^2)\) | \(O(N)\) |
| Giới hạn \(N\) | ~5000 | ~\(10^6\) |
| Yêu cầu | Không | \(M\) nguyên tố |
Tư duy cốt lõi¶
Cách 1: Tam giác Pascal — Dùng trực tiếp công thức truy hồi \(C(i, j) = C(i-1, j-1) + C(i-1, j) \bmod M\).
Cách 2: Factorial + Modular Inverse — Tính \(C(n, k) = n! \cdot (k!)^{-1} \cdot ((n-k)!)^{-1} \bmod M\).
Nghịch đảo modulo tính bằng Fermat: \(a^{-1} \equiv a^{M-2} \pmod{M}\) (vì \(M\) nguyên tố).
Để tối ưu, tính mảng inv_fact bằng cách đi ngược từ \(n\):
Phân tích tính đúng đắn¶
Cách 1: Đúng trực tiếp theo định lý Pascal, mọi giá trị đều lấy modulo nên không tràn.
Cách 2: Theo định lý nhỏ Fermat, với \(M\) nguyên tố và \(\gcd(a, M) = 1\):
Do đó \((k!)^{-1} \equiv (k!)^{M-2} \pmod{M}\), và công thức \(C(n, k) = \frac{n!}{k!(n-k)!}\) chuyển thành phép nhân modulo.
Code¶
Đánh giá độ phức tạp¶
Cách 1 — Tam giác Pascal:
- Preprocess: \(O(N^2)\) thời gian, \(O(N^2)\) bộ nhớ
- Truy vấn: \(O(1)\)
- Phù hợp khi \(N \le 5000\)
Cách 2 — Factorial + Inverse:
- Preprocess: \(O(N)\) thời gian, \(O(N)\) bộ nhớ
- Truy vấn: \(O(1)\)
- Phù hợp khi \(N \le 10^6\), yêu cầu \(M\) nguyên tố
3. Các biến thể tổ hợp¶
3.1. Hoán vị có lặp (Multinomial)¶
Bản chất vấn đề¶
Cho \(n\) đồ vật, trong đó có \(n_1\) đồ loại 1, \(n_2\) đồ loại 2, ..., \(n_k\) đồ loại k (\(n_1 + n_2 + \ldots + n_k = n\)). Hỏi có bao nhiêu cách sắp xếp?
Tư duy cốt lõi¶
Nếu tất cả \(n\) đồ vật khác nhau, có \(n!\) cách sắp xếp. Nhưng \(n_i\) đồ cùng loại hoán vị cho nhau không tạo cách mới, nên phải chia đi \(n_1! \cdot n_2! \cdots n_k!\).
Phân tích tính đúng đắn¶
Đây là tổng quát hóa của \(C(n, k)\). Khi \(k = 2\) với \(n_1 = k, n_2 = n-k\), công thức trở thành:
Ví dụ¶
Sắp xếp các ký tự trong "MISSISSIPPI": M(1), I(4), S(4), P(2), tổng 11 ký tự.
3.2. Tổ hợp có lặp (Combination with repetition)¶
Bản chất vấn đề¶
Chọn \(k\) đồ từ \(n\) loại, được phép chọn lại. Hỏi có bao nhiêu cách chọn?
Tư duy cốt lõi¶
Mỗi cách chọn tương ứng với một nghiệm không âm của phương trình:
Số nghiệm bằng \(C(n+k-1, k)\) (dùng kỹ thuật "stars and bars").
Phân tích tính đúng đắn¶
Biến đổi: đặt \(y_i = x_i + 1\), ta được \(y_1 + y_2 + \ldots + y_n = k + n\) với \(y_i \ge 1\). Số nghiệm dương bằng \(C(k+n-1, n-1) = C(n+k-1, k)\).
Ví dụ¶
Chọn 3 viên kẹo từ 5 loại, được chọn lại:
3.3. Catalan Numbers¶
Bản chất vấn đề¶
Dãy Catalan \(C_n\) xuất hiện trong nhiều bài toán đếm: cây nhị phân, dãy ngoặc đúng, phân hoạch đa giác, ...
Tư duy cốt lõi¶
Công thức truy hồi:
Công thức trực tiếp:
Ký hiệu \(\binom{n}{k}\) (đọc "n chập k") là cách viết khác của \(C(n,k) = \frac{n!}{k!(n-k)!}\).
Phân tích tính đúng đắn¶
Công thức trực tiếp suy ra từ công thức truy hồi bằng sinh hàm (generating function). Dãy Catalan: \(1, 1, 2, 5, 14, 42, 132, 429, \ldots\)
Ứng dụng:
- Số cách đặt dấu ngoặc đúng cho biểu thức \(n\) toán tử
- Số cây nhị phân có \(n\) node
- Số đường đi trên lưới \(n \times n\) không vượt qua đường chéo
- Số cách chia đa giác lồi \((n+2)\) cạnh thành tam giác
Code¶
Đánh giá độ phức tạp¶
- Công thức truy hồi: \(O(N^2)\) thời gian, \(O(N)\) bộ nhớ
- Công thức trực tiếp: \(O(\log M)\) mỗi truy vấn (sau khi preprocess factorial \(O(N)\))
4. Xác suất cơ bản¶
Bản chất vấn đề¶
Xác suất của biến cố \(A\):
Các quy tắc:
| Quy tắc | Công thức |
|---|---|
| Xác suất có điều kiện | \(P(A \cap B) = P(A) \cdot P(B \mid A)\) |
| Xác suất hợp | \(P(A \cup B) = P(A) + P(B) - P(A \cap B)\) |
| Bổ sung | \(P(\bar{A}) = 1 - P(A)\) |
Tư duy cốt lõi¶
Bài toán tung xúc xắc: Tung 2 xúc xắc, xác suất được tổng bằng 7?
- Kết quả thuận lợi: \((1,6), (2,5), (3,4), (4,3), (5,2), (6,1)\) → 6 cách
- Tổng kết quả: \(6 \times 6 = 36\)
- \(P = \frac{6}{36} = \frac{1}{6}\)
Bài toán tung đồng xu: Tung \(n\) đồng xu, xác suất được đúng \(k\) mặt ngửa?
Phân tích tính đúng đắn¶
- Số cách chọn \(k\) đồng xu ra mặt ngửa: \(C(n, k)\)
- Mỗi đồng xu có 2 trạng thái → tổng \(2^n\) kết quả
- Mọi kết quả đều có xác suất bằng nhau (đồng xu công bằng)
Code tính xác suất modulo¶
Khi tính xác suất modulo, phép chia chuyển thành nhân với nghịch đảo:
Đánh giá độ phức tạp¶
- Tính \(C(n, k)\): \(O(1)\) sau preprocess
- Tính \(2^n\): \(O(\log n)\) bằng lũy thừa nhị phân
- Tổng: \(O(\log n)\) mỗi truy vấn
5. Xác suất trên DP¶
Bản chất vấn đề¶
Nhiều bài toán xác suất phức tạp không có công thức đóng, cần dùng quy hoạch động (DP) để tính từng bước.
Tư duy cốt lõi¶
Ví dụ: Tung \(n\) đồng xu, xác suất có nhiều hơn \(\frac{n}{2}\) mặt ngửa?
Gọi \(dp[i][j]\) = xác suất được \(j\) mặt ngửa sau \(i\) lần tung.
Công thức:
- \(dp[i-1][j] \cdot \frac{1}{2}\): lần thứ \(i\) ra sấp
- \(dp[i-1][j-1] \cdot \frac{1}{2}\): lần thứ \(i\) ra ngửa
Kết quả: \(\sum_{j = \lfloor n/2 \rfloor + 1}^{n} dp[n][j]\)
Phân tích tính đúng đắn¶
Đây là bài toán phân phối nhị thức (binomial distribution). \(dp[i][j]\) chính là:
DP tính đúng vì mỗi trạng thái \((i, j)\) được xây dựng từ hai trạng thái hợp lệ trước đó.
Code¶
Đánh giá độ phức tạp¶
- Thời gian: \(O(N^2)\) — duyệt qua mọi trạng thái \((i, j)\)
- Bộ nhớ: \(O(N^2)\) — lưu bảng \(dp\). Có thể tối ưu xuống \(O(N)\) vì chỉ cần hàng trước.
6. Kỳ vọng (Expected Value)¶
Bản chất vấn đề¶
Kỳ vọng của biến ngẫu nhiên \(X\):
Tư duy cốt lõi¶
Ví dụ 1: Tung xúc xắc công bằng, kỳ vọng = \(\sum_{i=1}^{6} i \cdot \frac{1}{6} = 3.5\)
Ví dụ 2: Kỳ vọng số lần tung xúc xắc cho đến khi được mặt 6:
Đây là phân phối hình học: \(E = \frac{1}{p}\) với \(p\) là xác suất thành công mỗi lần.
Phân tích tính đúng đắn¶
Công thức kỳ vọng đúng cho mọi phân phối xác suất. Khi xác suất đều (như xúc xắc công bằng), có thể tính đơn giản:
Khi xác suất không đều, bắt buộc dùng công thức tổng quát \(E = \sum x \cdot P(x)\).
Đánh giá độ phức tạp¶
Tính kỳ vọng thường kết hợp với DP: \(O(N^2)\) hoặc \(O(N)\) tùy bài toán.
7. Lưu ý / Cạm bẫy hay gặp¶
Bẫy 1: Phép chia modulo ≠ phép chia thường¶
Lý do: Modular arithmetic không có phép chia trực tiếp. Phải chuyển sang nhân với nghịch đảo modulo.
Bẫy 2: Tràn số khi tính factorial¶
Bẫy 3: Tam giác Pascal tốn bộ nhớ¶
Bẫy 4: Catalan — Quên edge case và chia modulo¶
Bẫy 5: Sai điều kiện trong nCk¶
Bẫy 6: Quên tính nghịch đảo factorial đúng cách¶
Mẹo thi cử¶
- \(C(n, k)\) với \(n\) lớn: Dùng factorial + modular inverse
- \(C(n, k)\) với \(n\) nhỏ (\(\le 5000\)): Dùng tam giác Pascal
- Xác suất modulo: Phép chia → dùng
powerMod(b, MOD-2, MOD) - Tránh tràn số: Luôn lấy modulo sau mỗi phép nhân
- Catalan: Xuất hiện trong nhiều bài toán đếm hình học, cây, dãy ngoặc
- Expected Value: Thường kết hợp với DP
8. Bài tập luyện tập¶
| Bài | Nền tảng | Độ khó | Chủ đề |
|---|---|---|---|
| CSES - Binomial Coefficients | CSES | ⭐⭐ | \(C(n,k) \bmod p\) |
| CSES - Creating Strings II | CSES | ⭐⭐ | Hoán vị có trùng |
| CSES - Distributing Apples | CSES | ⭐⭐ | Tổ hợp có lặp |
| VNOJ - Atcoder DP Contest I - Coins | VNOJ | ⭐⭐ | Probability DP |
| VNOJ - VOMARBLE | VNOJ | ⭐⭐⭐ | Combinatorics |
| CSES - Counting Necklaces | CSES | ⭐⭐⭐ | Burnside's lemma |
| LeetCode - Unique Paths | LC | ⭐⭐ | \(C(n,k)\) cơ bản |
Bài viết liên quan¶
Tài liệu tham khảo¶
- VNOI Wiki - Cách tính số tổ hợp
- CP-Algorithms - Binomial Coefficients
- HackerEarth - Combinatorics
- GeeksforGeeks - nCr using Pascal's Triangle
Bài tiếp theo: Manacher →