Tổ hợp
Đề bài
Cho 2 số nguyên $N$ và $K$ $(1 \le K \le N \le 20)$. Hãy liệt kê tất cả các tổ hợp $K$ số có thể được chọn từ phạm vi $1 \to N$. Các số được in ra theo thứ tự từ điển tăng dần.
Input: gồm 2 số nguyên $N$ và $K$.
Output: in ra các tập con thỏa mãn, mỗi tập con trên một dòng.
4 21 2
1 3
1 4
2 3
2 4
3 44 31 2 3
1 2 4
1 3 4
2 3 4Lời mở đầu
Khác với chỉnh hợp, tổ hợp không phân biệt thứ tự: $\{1, 2\}$ và $\{2, 1\}$ là một. Cách tự nhiên để mỗi tập chỉ xuất hiện một lần là luôn viết nó theo thứ tự tăng dần. Mình đi 2 cách quay lui: cách 1 "chọn hay không chọn từng số", cách 2 "chọn số cho từng vị trí, số sau lớn hơn số trước" kèm một cận trên giúp cây không có nhánh thừa.
Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.
Nộp bài trên CLUE OJLộ trình lời giải, bấm vào từng phần để nhảy tới:
Bộ testBộ test chính thức có gì?
Bộ test có 100 test. Đề không chia subtask, nhưng $n$ tăng dần theo 3 nhóm rõ rệt nên mình chia 3 mức (2 cột cuối là số test đúng của 2 cách, chạy thật):
| Mức | Các test | n | k | Số dòng $C_n^k$ | Test có k = n | Cách 1 | Cách 2 |
|---|---|---|---|---|---|---|---|
| 1 | 1 – 30 | 1 … 8 | 1 … 8 | 1 … 70 | 8 | 30/30 | 30/30 |
| 2 | 31 – 70 | 9 … 15 | 1 … 15 | 1 … 5.005 | 2 | 40/40 | 40/40 |
| 3 | 71 – 100 | 16 … 20 | 2 … 19 | 1 … 184.756 | 2 | 30/30 | 30/30 |
Test lớn nhất là $n = 20, k = 10$ với $C_{20}^{10} = 184.756$ dòng (khoảng 5.081 KB output). Có cả những test $k = n$ (chỉ 1 dòng) – code phải xử lý được trường hợp này.
Ý tưởngSố sau luôn lớn hơn số trước
Ta điền $x_1 < x_2 < \cdots < x_k$. Ở vị trí $i$, số $x_i$ phải lớn hơn $x_{i-1}$ (đặt $x_0 = 0$). Nhưng nó cũng không được quá lớn: sau nó còn $k - i$ vị trí cần $k - i$ số lớn hơn nó, nên
Thử từ nhỏ tới lớn ở mọi vị trí nên kết quả ra đúng thứ tự từ điển. Em nhập $n, k$ rồi bấm Tiếp; nút đỏ ✗ là giá trị bị cận trên $n - k + i$ chặn lại, không cần thử:
Cách 1Chọn hay không chọn từng số
Cách nghĩ quen thuộc của bài "tập con": xét lần lượt các số $v = 1, 2, \ldots, n$, mỗi số có 2 nhánh – chọn hoặc không chọn. Đủ $k$ số thì in. Vì nhánh "chọn" được đi trước, các tập chứa số nhỏ hơn được in trước → đúng thứ tự từ điển.
- Phải kiểm tra
chon.size() == ktrướcv > n: tổ hợp có chứa số $n$ chỉ đủ $k$ số khi đã gọi tớiTry(n + 1). chon.pop_back()là bước quay lui: bỏ $v$ ra rồi đi nhánh "không chọn $v$".
Kết quả chạy thật: C++: đúng 100/100 test, test chậm nhất chạy 0.078 giây. Bản Python: Python: đúng 100/100 test, test chậm nhất chạy 0.301 giây.
Cây nhị phân có tới $2^{n}$ nhánh, nhiều nhánh đi tới cuối mà không đủ $k$ số (công thừa). Với $n = 20$ thì $2^{20} \approx 10^6$, vẫn chạy rất nhanh.
Cách 2Chọn số cho từng vị trí, có cận trên
x[0] = 0(biến toàn cục) nên vị trí 1 bắt đầu thử từ 1.- Cận trên
v <= n - k + iđảm bảo mọi nhánh đều đi tới được một tổ hợp: không có nhánh cụt, số lá của cây đúng bằng $C_n^k$.
Kết quả chạy thật: C++: đúng 100/100 test, test chậm nhất chạy 0.077 giây. Bản Python: Python: đúng 100/100 test, test chậm nhất chạy 0.227 giây.
n - k + i.
Nhận xét
Đăng nhận xét