Quy hoạch động trạng thái bit (bitmask)
Có n người và n việc, người i làm việc j tốn cij. Phân mỗi người một việc khác nhau, tổng chi phí nhỏ nhất bao nhiêu? Thử mọi cách phân là n!, với n = 15 là khoảng 1,3 × 1012. Nhưng khi phân lần lượt cho người 1, 2, …, điều duy nhất cần nhớ là tập các việc đã có người làm: không cần biết việc nào giao cho ai. Có 2n tập như vậy, với n = 15 chỉ khoảng 3 × 104.
Tập con là một số nguyên
Phần tiêu đề “Tập con là một số nguyên”Tập con của các số 0, 1, …, n - 1 được viết bằng n bit: bit thứ i bằng 1 khi phần tử i có trong tập. Ví dụ với n = 4, tập gồm 0, 2, 3 là 1101 (đọc từ phải sang: bit 0, bit 1, …), tức là số 13. Các thao tác trên tập thành phép toán bit:
| Thao tác | Viết bằng bit |
|---|---|
| i có trong tập S không | S >> i & 1 |
| thêm i vào S | S | (1 << i) |
| bỏ i khỏi S | S & ~(1 << i) |
| số phần tử của S | số bit 1 của S |
| tập đầy đủ | (1 << n) - 1 |
Bài toán phân công
Phần tiêu đề “Bài toán phân công”Gọi f[S] là chi phí nhỏ nhất khi đã phân xong cho |S| người đầu tiên (người 0, 1, …, |S| - 1) và S là tập các việc họ làm. Người tiếp theo là người thứ k = |S|, giao cho một việc j chưa có trong S. Gọi T là tập S thêm việc j:
f[T] = min(f[T], f[S] + ckj)
Bắt đầu f[tập rỗng] = 0, đi S từ nhỏ đến lớn (số nhỏ hơn đến trước, mà T luôn lớn hơn S). Đáp số là f[tập đầy đủ]. Cả bài khoảng 2n × n bước.
import sysdu_lieu = sys.stdin.read().split()n = int(du_lieu[0])c = [list(map(int, du_lieu[1 + i * n:1 + (i + 1) * n])) for i in range(n)]VO_CUNG = float('inf')f = [VO_CUNG] * (1 << n)f[0] = 0for S in range(1 << n): if f[S] == VO_CUNG: continue k = bin(S).count('1') # người tiếp theo if k == n: continue for j in range(n): if not (S >> j & 1): # việc j chưa có người làm T = S | (1 << j) if f[S] + c[k][j] < f[T]: f[T] = f[S] + c[k][j]print(f[(1 << n) - 1])#include <iostream>#include <vector>#include <algorithm>using namespace std;
int main() { int n; cin >> n; vector<vector<long long>> c(n, vector<long long>(n)); for (auto &hang : c) for (auto &x : hang) cin >> x; const long long VO_CUNG = 4e18; vector<long long> f(1 << n, VO_CUNG); f[0] = 0; for (int S = 0; S < (1 << n); S++) { if (f[S] == VO_CUNG) continue; int k = __builtin_popcount(S); // người tiếp theo if (k == n) continue; for (int j = 0; j < n; j++) if (!(S >> j & 1)) { // việc j chưa có người làm int T = S | (1 << j); f[T] = min(f[T], f[S] + c[k][j]); } } cout << f[(1 << n) - 1] << "\n"; return 0;}Bài toán người du lịch
Phần tiêu đề “Bài toán người du lịch”Đi qua mỗi thành phố đúng một lần rồi quay về chỗ cũ, quãng đường ngắn nhất. Lần này ngoài tập đã đi còn phải biết đang đứng ở đâu: g[S][i] là quãng đường ngắn nhất xuất phát từ thành phố 0, đã đi qua đúng tập S và đang đứng ở i (i thuộc S). Đi tiếp tới j chưa thuộc S, với T là S thêm j:
g[T][j] = min(g[T][j], g[S][i] + dij)
Đáp số là min của g[tập đầy đủ][i] + di0. Có 2n × n trạng thái, mỗi trạng thái thử n bước tiếp, khoảng 2n × n2 bước: n = 16 là khoảng 1,7 × 107.
Duyệt mọi tập con của một tập
Phần tiêu đề “Duyệt mọi tập con của một tập”Nhiều bài cần, với mỗi tập S, xét mọi tập con T của S. Vòng lặp sau đi qua mọi tập con khác rỗng của S theo thứ tự giảm dần:
T = Swhile T > 0: # xử lý tập con T của S T = (T - 1) & STrừ 1 rồi AND với S cho tập con lớn nhất của S nhỏ hơn T. Duyệt như vậy cho mọi S thì tổng số bước là 3n (mỗi phần tử có ba trạng thái: không thuộc S, thuộc S nhưng không thuộc T, thuộc T).
Lưu ý: 2n tăng rất nhanh. Quy hoạch động bitmask chỉ dùng được khi n khoảng 20 trở xuống (220 khoảng 106); duyệt tập con (3n) chỉ được khi n khoảng 15.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 15 bài, chia thành 5 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. Chọn dần từng phần tử
Phần tiêu đề “Nhóm 1. Chọn dần từng phần tử”Bài 1. Phân công việc
Phần tiêu đề “Bài 1. Phân công việc”Có n người và n việc. Dòng đầu có n (1 ≤ n ≤ 16), n dòng sau mỗi dòng có n số: số thứ j của dòng i là chi phí cij khi người i làm việc j (0 ≤ cij ≤ 106). Mỗi người làm đúng một việc, mỗi việc đúng một người. In ra tổng chi phí nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
34 2 84 3 73 1 6 | 12 |
Kiểm tra: nhập
57 3 9 5 24 8 1 6 79 2 6 3 85 6 4 9 13 7 2 8 6Bài 2. Đếm cách xếp chỗ
Phần tiêu đề “Bài 2. Đếm cách xếp chỗ”Có n học sinh và n ghế. Học sinh i chỉ chịu ngồi một số ghế. Dòng đầu có n (1 ≤ n ≤ 16), n dòng sau mỗi dòng là xâu n ký tự 0/1: ký tự thứ j của dòng i là 1 nếu học sinh i chịu ngồi ghế j. In ra số cách xếp mỗi học sinh một ghế khác nhau mà ai cũng vừa ý.
Ví dụ:
| Nhập | In ra |
|---|---|
3110011101 | 2 |
Kiểm tra: nhập
51110001110001111001111001Bài 3. Thang máy
Phần tiêu đề “Bài 3. Thang máy”Có n người, người thứ i nặng wi. Thang máy chở được tổng khối lượng không quá X mỗi chuyến. Dòng đầu có n, X (1 ≤ n ≤ 16, 1 ≤ wi ≤ X ≤ 109), dòng thứ hai có n số wi. In ra số chuyến ít nhất để chở hết mọi người.
Ví dụ:
| Nhập | In ra |
|---|---|
4 104 8 6 1 | 2 |
Kiểm tra: nhập
8 2013 7 5 9 2 11 6 8Bài 4. Thứ tự ít phàn nàn nhất
Phần tiêu đề “Bài 4. Thứ tự ít phàn nàn nhất”n bạn xếp thành một hàng. Nếu bạn i đứng trước bạn j (không cần liền kề) thì có cij lời phàn nàn. Dòng đầu có n (1 ≤ n ≤ 16), n dòng sau mỗi dòng có n số cij (0 ≤ cij ≤ 1000, cii = 0). In ra tổng số lời phàn nàn nhỏ nhất trên mọi cách xếp hàng.
Ví dụ:
| Nhập | In ra |
|---|---|
30 5 12 0 43 6 0 | 7 |
Kiểm tra: nhập
50 3 8 1 57 0 2 6 42 9 0 3 18 1 5 0 24 6 7 3 0Nhóm 2. Tập đã đi và vị trí hiện tại
Phần tiêu đề “Nhóm 2. Tập đã đi và vị trí hiện tại”Bài 5. Người du lịch
Phần tiêu đề “Bài 5. Người du lịch”Có n thành phố đánh số 0 đến n - 1, khoảng cách từ i đến j là dij (có thể khác dji). Dòng đầu có n (2 ≤ n ≤ 16), n dòng sau mỗi dòng có n số (0 ≤ dij ≤ 106, dii = 0). Xuất phát từ thành phố 0, đi qua mỗi thành phố đúng một lần rồi quay về 0. In ra tổng quãng đường ngắn nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
40 10 15 2010 0 35 2515 35 0 3020 25 30 0 | 80 |
Kiểm tra: nhập
50 3 9 7 24 0 6 3 89 5 0 2 67 3 4 0 52 8 6 1 0Bài 6. Đường đi qua mọi thành phố
Phần tiêu đề “Bài 6. Đường đi qua mọi thành phố”Như bài 5 nhưng được xuất phát từ thành phố bất kỳ và không cần quay về. Dòng đầu có n (1 ≤ n ≤ 16), n dòng sau mỗi dòng có n số dij. In ra tổng quãng đường ngắn nhất đi qua mỗi thành phố đúng một lần.
Ví dụ:
| Nhập | In ra |
|---|---|
40 10 15 2010 0 35 2515 35 0 3020 25 30 0 | 50 |
Kiểm tra: nhập
50 3 9 7 24 0 6 3 89 5 0 2 67 3 4 0 52 8 6 1 0Bài 7. Đếm đường đi qua mọi đỉnh
Phần tiêu đề “Bài 7. Đếm đường đi qua mọi đỉnh”Đồ thị có hướng n đỉnh (đánh số 1 đến n) và m cạnh. Dòng đầu có n, m (2 ≤ n ≤ 15, 0 ≤ m ≤ n × (n - 1)), m dòng sau mỗi dòng có u, v: cạnh một chiều từ u đến v (không có cạnh lặp). In ra số đường đi từ đỉnh 1 đến đỉnh n đi qua mỗi đỉnh đúng một lần.
Ví dụ:
| Nhập | In ra |
|---|---|
4 61 21 32 33 22 43 4 | 2 |
Kiểm tra: nhập
5 121 21 31 42 33 22 44 23 44 32 53 54 5Bài 8. Thu thập kho báu
Phần tiêu đề “Bài 8. Thu thập kho báu”Bản đồ là bảng h hàng, w cột gồm . (đi được), # (tường), S (vị trí xuất phát, có đúng một), T (kho báu, có không quá 10). Mỗi bước đi sang ô chung cạnh. Dòng đầu có h, w (1 ≤ h, w ≤ 50), h dòng sau là bản đồ. In ra số bước ít nhất để nhặt hết kho báu (thứ tự tùy ý, không cần quay về), hoặc -1 nếu không nhặt được hết.
Ví dụ:
| Nhập | In ra |
|---|---|
3 5S..T..#.#.T...T | 9 |
Kiểm tra: nhập
5 6S.#..T..#.#.T...#..##......T.TNhóm 3. Duyệt tập con
Phần tiêu đề “Nhóm 3. Duyệt tập con”Bài 9. Bữa tiệc không ai ghét nhau
Phần tiêu đề “Bài 9. Bữa tiệc không ai ghét nhau”Có n người, m cặp ghét nhau. Mời nhiều người nhất có thể sao cho không có hai người ghét nhau cùng được mời. Dòng đầu có n, m (1 ≤ n ≤ 20, 0 ≤ m ≤ n × (n - 1) / 2), m dòng sau mỗi dòng có u, v (1 ≤ u, v ≤ n, u khác v). In ra số người nhiều nhất được mời.
Ví dụ:
| Nhập | In ra |
|---|---|
5 51 22 33 44 55 1 | 2 |
Kiểm tra: nhập
8 101 21 32 43 44 55 66 77 88 52 6Bài 10. Chia nhóm hợp nhau nhất
Phần tiêu đề “Bài 10. Chia nhóm hợp nhau nhất”Có n bạn, mỗi cặp bạn i, j có độ hợp nhau aij (có thể âm). Chia n bạn thành các nhóm (số nhóm tùy ý, mỗi bạn đúng một nhóm). Điểm của cách chia là tổng aij trên mọi cặp i, j cùng nhóm. Dòng đầu có n (1 ≤ n ≤ 13), n dòng sau mỗi dòng có n số (aij = aji, aii = 0, |aij| ≤ 106). In ra điểm lớn nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
30 3 -13 0 2-1 2 0 | 4 |
Kiểm tra: nhập
50 4 -2 1 -34 0 5 -1 2-2 5 0 3 -41 -1 3 0 6-3 2 -4 6 0Bài 11. Ít người nhất đủ mọi kỹ năng
Phần tiêu đề “Bài 11. Ít người nhất đủ mọi kỹ năng”Dự án cần k kỹ năng đánh số 0 đến k - 1. Có n ứng viên, mỗi người có một số kỹ năng. Dòng đầu có n, k (1 ≤ n ≤ 100, 1 ≤ k ≤ 16), n dòng sau: dòng thứ i bắt đầu bằng số c rồi c kỹ năng của người i. In ra số người ít nhất cần tuyển để đủ cả k kỹ năng, hoặc -1 nếu không thể.
Ví dụ:
| Nhập | In ra |
|---|---|
4 31 02 0 11 22 1 2 | 2 |
Kiểm tra: nhập
6 52 0 12 1 22 3 41 43 0 2 42 1 3Nhóm 4. Tổng trên tập con
Phần tiêu đề “Nhóm 4. Tổng trên tập con”Bài 12. Tổng trên mọi tập con
Phần tiêu đề “Bài 12. Tổng trên mọi tập con”Dòng đầu có k (1 ≤ k ≤ 20), dòng thứ hai có 2k số nguyên a0, …, a2k - 1 (|ai| ≤ 106). Với mỗi S từ 0 đến 2k - 1, tính bS = tổng aT qua mọi T là tập con của S (mọi bit của T đều có trong S). In ra b0, …, b2k - 1.
Ví dụ:
| Nhập | In ra |
|---|---|
21 2 3 4 | 1 3 4 10 |
Kiểm tra: nhập
35 -1 2 0 3 7 -4 1Bài 13. Cặp số không chung bit
Phần tiêu đề “Bài 13. Cặp số không chung bit”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (0 ≤ ai nhỏ hơn 220). In ra số cặp vị trí i đứng trước j mà ai AND aj = 0.
Ví dụ:
| Nhập | In ra |
|---|---|
41 2 3 4 | 4 |
Kiểm tra: nhập
80 5 10 3 12 0 7 8Nhóm 5. Quy hoạch động theo hàng
Phần tiêu đề “Nhóm 5. Quy hoạch động theo hàng”Bài 14. Lát gạch domino
Phần tiêu đề “Bài 14. Lát gạch domino”Bảng có n hàng, m cột. Đếm số cách lát kín bảng bằng các viên gạch 1 × 2 (đặt ngang hoặc dọc, không chồng lên nhau, không thừa ra ngoài). Dòng duy nhất có n, m (1 ≤ n ≤ 1000, 1 ≤ m ≤ 8). In ra số cách, chia lấy dư 109 + 7.
Ví dụ: nhập 2 3 thì in ra 3.
Kiểm tra: nhập 10 6.
Bài 15. Đặt vua không ăn nhau
Phần tiêu đề “Bài 15. Đặt vua không ăn nhau”Bảng n hàng, m cột. Đặt các quân vua vào một số ô sao cho không có hai quân nào kề nhau (chung cạnh hoặc chung góc). Dòng duy nhất có n, m (1 ≤ n ≤ 100, 1 ≤ m ≤ 10). In ra số cách đặt (kể cả không đặt quân nào), chia lấy dư 109 + 7.
Ví dụ: nhập 2 2 thì in ra 5.
Kiểm tra: nhập 8 8.