Các bạn nhớ xem lại các thao tác với bit, lấy bit, đảo bit. ## Bài 1: [Bảng nhị phân](https://marisaoj.com/problem/378) - Công thức DP: $f(i, mask)$, xây đến cột thứ $i$, các giá trị điền vào cột thể hiện bởi $mask$. - Lưu ý trong $mask$ không được có hai bit nào cạnh nhau: - Với mỗi $mask$, tìm $mask_2$ nằm ở cột $i-1$, nếu $mask \text{ AND } mask_2=0$, nghĩa là $mask$ có thể nằm cạnh $mask_2$ do không có các bit $1$ kề nhau, cập nhật $f(i-1, mask_2)$ lên $f(i,mask)$ - Đáp án à $\sum{f(m,mask)}$ do hàng $m$ có thể là bất cứ $mask$ nào. Code: :::spoiler ```c++ #include <iostream> #include <vector> const int MOD = 1000000007; int countValidBoards(int n, int m) { std::vector<std::vector<int>> dp(1 << m, std::vector<int>(n + 1)); // Base case dp[0][0] = 1; for (int row = 1; row <= n; ++row) { for (int mask = 0; mask < (1 << m); ++mask) { for (int newMask = 0; newMask < (1 << m); ++newMask) { if ((newMask & (newMask << 1)) == 0 && (newMask & mask) == 0) { dp[newMask][row] = (dp[newMask][row] + dp[mask][row - 1]) % MOD; } } } } int total = 0; for (int mask = 0; mask < (1 << m); ++mask) { total = (total + dp[mask][n]) % MOD; } return total; } int main() { int n, m; std::cin >> m >> n; int result = countValidBoards(n, m); std::cout << result << std::endl; return 0; } ``` ::: ## Bài 2: [Bài toán người bán hàng 2](https://marisaoj.com/problem/215) - Công thức DP: $f(i, mask)$, hiện tại đang đứng ở vị trí $i$. Các bit $1$ trong $mask$ thể hiện các thành phố đã thăm. - Đánh số các thành phố từ $0$ đến $n-1$, trạng thái bắt đầu $f(0,1)=0$, ở đây, ta bắt đầu từ thành phố $0$, đánh dấu mask là $...0001_2=1$ là đã thăm. - Vỉ sao bắt đầu ở $0$, vì đề bài yêu cầu đi thành một chu trình khép kín, nên dù bắt đầu ở đâu thì vẫn phải đi thành một vòng. - Với mỗi $f(i,mask)$, ta sẽ chọn một thành phố $j$ sao cho bit $j$ trong $mask$ là $0$, nghĩa là chưa được thăm, cập nhật $f(j,mask \text{ OR } 1 \ll j) = min(f(j,mask \text{ OR } 1 \ll j), f(i,mask)+A_{i,j})$. - Đáp án là $f(x, 2^n-1) + A_{x,0}$ với $x$ là một TP khác $0$, phải quay về $0$ nên cộng thêm $A_{x,0}$. - Code :::spoiler ```c++ #include <bits/stdc++.h> using namespace std; const int maxn = 21, oo = 1e9; int n; int c[21][21]; int dp[(1<<21)][21]; signed main() { cin.tie(0); ios_base::sync_with_stdio(false); cin >> n; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cin >> c[i][j]; } } memset(dp, 0x3f, sizeof dp); dp[1][0]=0; for (int mask = 0; mask < (1 << n); mask++) { for (int i = 0; i < n; i++) { if (((mask >> i) & 1) == 0) continue; for (int j = 0; j < n; j++) { if (((mask >> j) & 1) == 1) continue; dp[mask | (1 << j)][j] = min(dp[mask | (1 << j)][j], dp[mask][i] + c[i][j]); } } } int res = 1e9; for (int i = 1; i < n; i++) { res = min(res, dp[(1 << n) - 1][i] + c[i][0]); } cout << res << '\n'; } ``` ::: ## Bài 4: [Pha thuốc 5](https://marisaoj.com/problem/221) - Công thức: $f(mask)$ là đã chọn những cây nấm thể hiện trong $mask$, sức mạnh tối đa là bao nhiêu. Base case $f(0)=0$, chưa chọn cây nấm nào nên sức mạnh là $0$. - Với mỗi $f(mask)$, chọn ra $i$ và $j$ sao cho bit $i$ và $j$ trong mask là $0$, cập nhật $f(mask \text{ OR } 1 \ll i \text{ OR } 1 \ll j) = max(f(mask) + A_{i,j})$. Cho thêm cây nấm thứ $i$ và $j$ tạo thành một lọ thuốc. - Đáp án là $f(2^{n\times2}-1)$, khi sử dụng hết các cây nấm. - Code :::spoiler ```c++ #include <bits/stdc++.h> using namespace std; #define ll int #define fi first #define sc second const int inf = 1e8; ll dp[1 << 20][20]; ll n; ll a[20][20]; void inp() { cin >> n; for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < 2 * n; j++) { cin >> a[i][j]; } } } ll solve(ll x, ll mask) { if (x == n) return 0; if (dp[mask][x] != -1) return dp[mask][x]; ll ans = -inf; for (int i = 0; i < 2 * n; i++) { if (!(mask & (1 << i))) { for (int j = 0; j < i; j++) { if (!(mask & (1 << j))) { ans = max(ans, solve(x + 1, mask | (1 << i) | (1 << j)) + a[i][j]); } } } } return dp[mask][x] = ans; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); inp(); memset(dp, -1, sizeof dp); cout << solve(0, 0); } ``` :::