Chương 4·Bài 44·Nâng cao

Broken Profile DP

Lát bảng bằng domino, chọn ô không kề nhau, đặt trạm không nhiễu: lưới có một cạnh ngắn (tới 10 đến 12 ô) và một cạnh dài. Duyệt lưới theo một thứ tự cố định thì phần đã quyết định và phần chưa quyết định chỉ chạm nhau trên một đường biên; mọi thứ cần nhớ về quá khứ nằm trên biên đó, đủ ngắn để mã hóa bằng một mặt nạ bit (bài 15). Biên có thể là cả một hàng (quy hoạch động theo hàng) hoặc một đường bậc thang “gãy” tại ô đang xét (quy hoạch động theo từng ô), cho chuyển tiếp rẻ hơn hẳn.

Biên gãy theo từng ô

Lát bảng n×mn \times m bằng domino, nn nhỏ. Duyệt từng cột từ trái sang, trong mỗi cột từ trên xuống. Khi đang ở ô (r,c)(r, c), biên đi dọc cột cc từ hàng rr xuống, rồi gãy sang cột c+1c + 1 cho các hàng phía trên rr. Bit jj của mặt nạ cho biết ô trên biên ở hàng jj đã bị phủ chưa: với j≥rj \ge r đó là ô (j,c)(j, c), với j<rj < r đó là ô (j,c+1)(j, c + 1), bị phủ bởi domino ngang đặt từ cột cc.

Ô viền cam là ô vừa xử lý, ô vàng là biên gãy mà mặt nạ mô tả; bảng bên phải là số cách theo mặt nạ.

Tại ô (r,c)(r, c) với bit rr:

  • Bit rr đã bật: ô đã phủ, không làm gì; bit rr chuyển sang nói về ô (r,c+1)(r, c + 1), chưa phủ, nên tắt.
  • Bit rr tắt: đặt domino ngang sang (r,c+1)(r, c + 1), bật bit rr; hoặc đặt domino dọc xuống (r+1,c)(r + 1, c) nếu còn hàng và bit r+1r + 1 tắt, khi đó bật bit r+1r + 1 còn bit rr tắt.

Mỗi ô có 2n2^n trạng thái và tối đa hai chuyển tiếp: O(nm2n)O(nm 2^n). Làm theo cả cột thì mỗi cặp mặt nạ (cột trước, cột sau) phải kiểm riêng, O(m4n)O(m 4^n).

Hỏi nhanh

Đang ở ô (r,c)(r, c) với bit rr tắt và bit r+1r + 1 bật. Đặt được gì ở ô này?

Ví dụLát Kín Bảng Chữ Nhật

Đề. Bảng n≤10n \le 10 hàng, m≤1000m \le 1000 cột. Đếm số cách lát kín bằng domino, modulo 109+710^9 + 7.

Lời giải. Duyệt ô theo từng cột, trong cột theo từng hàng. Mặt nạ s có nn bit: bit rr cho biết ô ở hàng rr của cột đang xét đã bị phủ sẵn chưa, bởi domino ngang từ cột trước hay domino dọc từ ô ngay trên.

Bước 1: ô đã bị phủ. Không đặt gì; xóa bit rr, vì từ giờ bit đó nói về ô hàng rr của cột c+1c + 1.

C++
if (s >> r & 1) nd[s ^ (1 << r)] = (nd[s ^ (1 << r)] + v) % MOD;

Bước 2: ô còn trống. Đặt domino ngang sang cột sau thì bật bit rr (cho cột c+1c + 1), chỉ khi còn cột sau. Đặt domino dọc xuống ô (r+1,c)(r + 1, c) thì bật bit r+1r + 1, chỉ khi ô đó cũng còn trống.

C++
else {
    if (c + 1 < m) nd[s | (1 << r)] = (nd[s | (1 << r)] + v) % MOD;
    if (r + 1 < n && !(s >> (r + 1) & 1)) nd[s | (1 << (r + 1))] = (nd[s | (1 << (r + 1))] + v) % MOD;
}

Bước 3: chuyển bảng. Mỗi ô xóa nd, chuyển từ dp, rồi chép lại vào dp. Sau ô cuối cùng, dp[0] là số cách không ô nào của cột ảo m+1m + 1 bị phủ, tức lát vừa khít.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll MOD = 1e9 + 7;
ll n, m, dp[1 << 10], nd[1 << 10];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    ll full = 1 << n;
    dp[0] = 1;
    for (ll c = 0; c < m; c++)
        for (ll r = 0; r < n; r++) {
            for (ll s = 0; s < full; s++) nd[s] = 0;
            for (ll s = 0; s < full; s++) {
                if (!dp[s]) continue;
                ll v = dp[s];
                if (s >> r & 1) nd[s ^ (1 << r)] = (nd[s ^ (1 << r)] + v) % MOD;
                else {
                    if (c + 1 < m) nd[s | (1 << r)] = (nd[s | (1 << r)] + v) % MOD;
                    if (r + 1 < n && !(s >> (r + 1) & 1)) nd[s | (1 << (r + 1))] = (nd[s | (1 << (r + 1))] + v) % MOD;
                }
            }
            for (ll s = 0; s < full; s++) dp[s] = nd[s];
        }
    cout << dp[0] << '\n';
}
2 3

n⋅m⋅2n≈107n \cdot m \cdot 2^n \approx 10^7 bước.

Cả hàng làm trạng thái

Khi ràng buộc chỉ nối hai hàng liền nhau (không hai ô chọn chung cạnh, không ngồi chéo), lấy cả hàng làm mặt nạ: dp[i][s]dp[i][s] là số cách cho ii hàng đầu với hàng ii chọn tập ss. Chuyển tiếp duyệt mọi cặp (s′,s)(s', s) của hai hàng liền nhau. Trước hết lọc các mặt nạ tự hợp lệ trong một hàng: không hai bit kề nhau có Θ(φm)\Theta(\varphi^m) mặt nạ, với m=12m = 12 chỉ 377 trong 4096. Điều kiện giữa hai hàng thường là một phép bit: không chung cạnh dọc là s & s2 == 0, không chéo là (s << 1) & s2 == 0 và (s >> 1) & s2 == 0.

Hỏi nhanh

Với m=3m = 3 cột, những mặt nạ nào là hợp lệ trong một hàng (không hai ô chọn chung cạnh)?

Ví dụChọn Ô Không Chung Cạnh

Đề. Bảng N,M≤12N, M \le 12, mỗi ô được phép chọn hoặc không. Đếm số tập ô được phép, không hai ô nào chung cạnh, modulo 10810^8.

Lời giải. Quy hoạch động theo hàng, trạng thái là mặt nạ các ô chọn ở hàng vừa xét. Hai ô trong một hàng không được kề nhau; hai hàng liền nhau không được chọn cùng cột.

Bước 1: mặt nạ ô được phép. fr[i] bật bit jj khi ô (i,j)(i, j) được chọn.

C++
if (x) fr[i] |= 1 << j;

Bước 2: lọc mặt nạ một hàng. Mặt nạ không có hai bit kề khi s & (s << 1) bằng 0. Với m=12m = 12 chỉ còn 377 mặt nạ; lọc một lần, dùng cho mọi hàng.

C++
for (ll s = 0; s < (1 << m); s++)
    if (!(s & (s << 1))) ok.push_back(s);

Bước 3: chuyển hàng. Mặt nạ ss phải nằm gọn trong fr[i]; cộng dp của mọi mặt nạ tt ở hàng trước không giao ss. dp[0] = 1 trước hàng đầu, coi như có một hàng rỗng phía trên.

C++
for (ll s : ok) {
    if ((s & fr[i]) != s) continue;
    for (ll t : ok)
        if (!(s & t) && dp[t]) nd[s] = (nd[s] + dp[t]) % MOD;
}

Đáp án cộng mọi trạng thái của hàng cuối, kể cả mặt nạ rỗng (tập không chọn ô nào cũng được đếm).

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll MOD = 1e8;
ll n, m, fr[15], dp[1 << 12], nd[1 << 12];
vector<ll> ok;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    for (ll i = 0; i < n; i++)
        for (ll j = 0; j < m; j++) {
            ll x;
            cin >> x;
            if (x) fr[i] |= 1 << j;
        }
    for (ll s = 0; s < (1 << m); s++)
        if (!(s & (s << 1))) ok.push_back(s);
    dp[0] = 1;
    for (ll i = 0; i < n; i++) {
        for (ll s : ok) nd[s] = 0;
        for (ll s : ok) {
            if ((s & fr[i]) != s) continue;
            for (ll t : ok)
                if (!(s & t) && dp[t]) nd[s] = (nd[s] + dp[t]) % MOD;
        }
        for (ll s : ok) dp[s] = nd[s];
    }
    ll ans = 0;
    for (ll s : ok) ans = (ans + dp[s]) % MOD;
    cout << ans << '\n';
}
1 3
1 1 1

O(N⋅3772)≈1,7⋅106O(N \cdot 377^2) \approx 1{,}7 \cdot 10^6.

Ràng buộc kéo dài hai hàng

Nếu ràng buộc nối hàng ii với cả hàng i−1i - 1 và i−2i - 2 (trạm cách nhau tới 2 ô theo cột), trạng thái phải giữ hai hàng gần nhất: dp[i][a][b]dp[i][a][b] với aa là mặt nạ hàng ii, bb hàng i−1i - 1. Chuyển tiếp thử mặt nạ cc cho hàng i−2i - 2 cũ: điều kiện là a,b,ca, b, c đôi một không giao. Số mặt nạ tự hợp lệ rất nhỏ (không hai bit trong khoảng cách 2: với m=10m = 10 chỉ 60), nên O(N⋅603)O(N \cdot 60^3) vẫn nhanh. Lọc trước cũng giúp bỏ các mặt nạ đặt trạm lên ô cấm.

Ví dụTrạm Tín Hiệu Trên Lưới

Đề. Lưới N≤100N \le 100 hàng, M≤10M \le 10 cột, ô P đặt được trạm. Hai trạm cùng hàng hay cùng cột cách nhau 1 hoặc 2 ô thì nhiễu nhau. Đặt nhiều trạm nhất.

Lời giải. Trạm ở hàng ii nhiễu với trạm cùng cột ở hàng i−1i - 1 và i−2i - 2, nên trạng thái phải nhớ mặt nạ của cả hai hàng gần nhất.

Bước 1: mặt nạ một hàng. Không có hai trạm cách nhau 1 hay 2 ô: s & (s << 1) và s & (s << 2) đều bằng 0. Với m=10m = 10 chỉ còn 60 mặt nạ, nên trạng thái dùng chỉ số trong danh sách ok thay cho chính mặt nạ.

C++
for (ll s = 0; s < (1 << m); s++)
    if (!(s & (s << 1)) && !(s & (s << 2))) ok.push_back(s);

Bước 2: trạng thái. dp[a][b] là số trạm lớn nhất khi hàng hiện tại dùng ok[a], hàng trước dùng ok[b]; −1-1 là trạng thái không tồn tại. Bắt đầu với hai hàng rỗng: dp[0][0] = 0.

Bước 3: thêm hàng mới xx. Phải nằm trong ô P, không giao hàng ngay trên (aa) và hàng trên nữa (bb). Trạng thái mới là (x,a)(x, a), cộng số trạm của xx.

C++
for (ll x = 0; x < k; x++) {
    if ((ok[x] & fr[i]) != ok[x]) continue;
    ll c = __builtin_popcountll(ok[x]);
    for (ll a = 0; a < k; a++) {
        if (ok[x] & ok[a]) continue;
        for (ll b = 0; b < k; b++) {
            if (dp[a][b] < 0 || (ok[x] & ok[b])) continue;
            nd[x][a] = max(nd[x][a], dp[a][b] + c);
        }
    }
}

Đáp án là lớn nhất trên mọi trạng thái sau hàng cuối.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll n, m, fr[105], dp[65][65], nd[65][65];
vector<ll> ok;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    for (ll i = 0; i < n; i++) {
        string s;
        cin >> s;
        for (ll j = 0; j < m; j++)
            if (s[j] == 'P') fr[i] |= 1 << j;
    }
    for (ll s = 0; s < (1 << m); s++)
        if (!(s & (s << 1)) && !(s & (s << 2))) ok.push_back(s);
    ll k = ok.size();
    for (ll a = 0; a < k; a++)
        for (ll b = 0; b < k; b++) dp[a][b] = -1;
    dp[0][0] = 0;
    for (ll i = 0; i < n; i++) {
        for (ll a = 0; a < k; a++)
            for (ll b = 0; b < k; b++) nd[a][b] = -1;
        for (ll x = 0; x < k; x++) {
            if ((ok[x] & fr[i]) != ok[x]) continue;
            ll c = __builtin_popcountll(ok[x]);
            for (ll a = 0; a < k; a++) {
                if (ok[x] & ok[a]) continue;
                for (ll b = 0; b < k; b++) {
                    if (dp[a][b] < 0 || (ok[x] & ok[b])) continue;
                    nd[x][a] = max(nd[x][a], dp[a][b] + c);
                }
            }
        }
        for (ll a = 0; a < k; a++)
            for (ll b = 0; b < k; b++) dp[a][b] = nd[a][b];
    }
    ll ans = 0;
    for (ll a = 0; a < k; a++)
        for (ll b = 0; b < k; b++) ans = max(ans, dp[a][b]);
    cout << ans << '\n';
}
3 5
PHPHP
PPPPP
HPHPH

N⋅603≈2,2⋅107N \cdot 60^3 \approx 2{,}2 \cdot 10^7 bước.

Thêm thông tin vào bit cho bài tối ưu

Bit trên biên không chỉ nói “đã phủ hay chưa”; nó có thể mang bất kỳ thông tin nhị phân nào cần cho ô kế tiếp. Phủ sàn bằng ít thanh thẳng nhất: mỗi ô thuộc một thanh ngang hoặc một thanh dọc, và một thanh mới tốn 1 khi không nối tiếp được thanh cùng hướng ở ô bên cạnh. Duyệt theo hàng, bit jj là “ô gần nhất đã xét ở cột jj có thuộc thanh dọc không”. Ô (r,c)(r, c) đi dọc thì miễn phí nếu ô trên là sàn và bit cc bật; đi ngang thì miễn phí nếu ô trái là sàn và bit c−1c - 1 (đã cập nhật ở hàng này) tắt. Ô chắn đặt bit về 0 và không tốn gì.

Ví dụPhủ Sàn Bằng Các Thanh Thẳng

Đề. Bảng N,M≤10N, M \le 10 có ô sàn . và cột chắn #. Phủ mọi ô sàn bằng các thanh thẳng 1×K1 \times K hoặc K×1K \times 1 không chồng nhau, dùng ít thanh nhất.

Lời giải. Mỗi ô sàn thuộc một thanh ngang hoặc một thanh dọc. Số thanh bằng số ô “mở đầu” một thanh: ô dọc mà ô trên không đi dọc, ô ngang mà ô trái không đi ngang. Duyệt ô theo hàng, mặt nạ s nhớ hướng của mm ô gần nhất: bit jj bật khi ô gần nhất đã xét ở cột jj đi dọc (với cột <c< c là ô ở hàng hiện tại, với cột ≥c\ge c là ô ở hàng trên). dp[s]dp[s] là số thanh ít nhất.

Bước 1: ô chắn. Không thanh nào đi qua: xóa bit cc, không tốn gì.

C++
if (g[r][c] == '#') {
    ll t = s & ~(1LL << c);
    nd[t] = min(nd[t], v);
    continue;
}

Bước 2: ô sàn đi dọc. Miễn phí khi ô trên là sàn và cũng đi dọc (bit cc đang bật), ngược lại mở thanh mới tốn 1. Bật bit cc.

C++
ll up = r > 0 && g[r - 1][c] == '.' && (s >> c & 1);
ll t = s | (1LL << c);
nd[t] = min(nd[t], v + !up);

Bước 3: ô sàn đi ngang. Miễn phí khi ô trái là sàn và đi ngang (bit c−1c - 1, đã cập nhật ở hàng này, đang tắt). Xóa bit cc.

C++
ll left = c > 0 && g[r][c - 1] == '.' && !(s >> (c - 1) & 1);
t = s & ~(1LL << c);
nd[t] = min(nd[t], v + !left);

Đáp án là nhỏ nhất trên mọi mặt nạ sau ô cuối; bảng không có ô sàn cho 0.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll INF = 1e18;
ll n, m, dp[1 << 10], nd[1 << 10];
string g[12];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    for (ll i = 0; i < n; i++) cin >> g[i];
    ll full = 1 << m;
    for (ll s = 0; s < full; s++) dp[s] = INF;
    dp[0] = 0;
    for (ll r = 0; r < n; r++)
        for (ll c = 0; c < m; c++) {
            for (ll s = 0; s < full; s++) nd[s] = INF;
            for (ll s = 0; s < full; s++) {
                if (dp[s] == INF) continue;
                ll v = dp[s];
                if (g[r][c] == '#') {
                    ll t = s & ~(1LL << c);
                    nd[t] = min(nd[t], v);
                    continue;
                }
                ll up = r > 0 && g[r - 1][c] == '.' && (s >> c & 1);
                ll t = s | (1LL << c);
                nd[t] = min(nd[t], v + !up);
                ll left = c > 0 && g[r][c - 1] == '.' && !(s >> (c - 1) & 1);
                t = s & ~(1LL << c);
                nd[t] = min(nd[t], v + !left);
            }
            for (ll s = 0; s < full; s++) dp[s] = nd[s];
        }
    ll ans = INF;
    for (ll s = 0; s < full; s++) ans = min(ans, dp[s]);
    cout << ans << '\n';
}
2 2
..
..

2102^{10} trạng thái nhân 100 ô, khoảng 10510^5 bước.

Bảng rất dài: nhân ma trận chuyển

Khi chiều dài tới 101210^{12}, chuyển tiếp giữa hai cột liên tiếp giống hệt nhau ở mọi cột, nên là một ma trận TT kích thước 2n×2n2^n \times 2^n: T[a][b]T[a][b] là số cách điền một cột khi các ô trong aa đã bị domino ngang từ cột trước chiếm và các ô trong bb bắt đầu domino ngang sang cột sau. Đáp án là (TW)[0][0](T^W)[0][0], tính bằng lũy thừa nhị phân ma trận (bài 18) trong O(8nlog⁡W)O(8^n \log W). Mỗi ô T[a][b]T[a][b] đếm cách phủ các ô còn trống trong cột bằng những miếng nằm gọn trong cột.

Ví dụLát Bảng Rất Dài

Đề. Bảng H≤6H \le 6 hàng, W≤1012W \le 10^{12} cột, lát bằng ô 1×11 \times 1, domino ngang và domino dọc. Đếm số cách, modulo 998244353998244353.

Lời giải. WW tới 101210^{12} nên không thể đi từng cột; nhưng mọi cột chuyển như nhau, nên dựng ma trận chuyển TT một lần rồi lũy thừa. Trạng thái giữa hai cột là mặt nạ các ô bị domino ngang nối qua.

Bước 1: ma trận chuyển. aa là các ô của cột bị domino ngang từ cột trước chiếm, bb là các ô bắt đầu domino ngang sang cột sau. aa và bb giao nhau thì T[a][b]=0T[a][b] = 0. Các ô còn lại chia thành các đoạn liền; đoạn dài kk phủ bằng ô đơn và domino dọc theo fkf_k cách, với f0=f1=1f_0 = f_1 = 1, fk=fk−1+fk−2f_k = f_{k-1} + f_{k-2}. Vòng r chạy tới hh (dư một ô) để chốt đoạn cuối.

C++
for (ll a = 0; a < S; a++)
    for (ll b = 0; b < S; b++) {
        if (a & b) continue;
        ll ways = 1, run = 0;
        for (ll r = 0; r <= h; r++) {
            if (r < h && !((a | b) >> r & 1)) run++;
            else {
                ways *= f[run];
                run = 0;
            }
        }
        T[a][b] = ways % MOD;
    }

Bước 2: nhân ma trận. Ma trận 2h×2h2^h \times 2^h theo mô-đun; bỏ qua phần tử 0 của a để nhanh hơn.

C++
vector<vector<ll>> mul(vector<vector<ll>> a, vector<vector<ll>> b) {
    vector<vector<ll>> c(S, vector<ll>(S, 0));
    for (ll i = 0; i < S; i++)
        for (ll k = 0; k < S; k++) {
            if (!a[i][k]) continue;
            for (ll j = 0; j < S; j++) c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;
        }
    return c;
}

Bước 3: lũy thừa nhị phân. R bắt đầu là ma trận đơn vị. (TW)[0][0](T^W)[0][0] là số cách đi từ “không ô nào bị chiếm” trước cột đầu tới “không ô nào sang cột ảo” sau cột cuối.

C++
for (; w; w >>= 1) {
    if (w & 1) R = mul(R, T);
    T = mul(T, T);
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const ll MOD = 998244353;
ll h, w, S, f[10];
vector<vector<ll>> T;

vector<vector<ll>> mul(vector<vector<ll>> a, vector<vector<ll>> b) {
    vector<vector<ll>> c(S, vector<ll>(S, 0));
    for (ll i = 0; i < S; i++)
        for (ll k = 0; k < S; k++) {
            if (!a[i][k]) continue;
            for (ll j = 0; j < S; j++) c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;
        }
    return c;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> h >> w;
    S = 1 << h;
    f[0] = f[1] = 1;
    for (ll k = 2; k < 10; k++) f[k] = f[k - 1] + f[k - 2];
    T.assign(S, vector<ll>(S, 0));
    for (ll a = 0; a < S; a++)
        for (ll b = 0; b < S; b++) {
            if (a & b) continue;
            ll ways = 1, run = 0;
            for (ll r = 0; r <= h; r++) {
                if (r < h && !((a | b) >> r & 1)) run++;
                else {
                    ways *= f[run];
                    run = 0;
                }
            }
            T[a][b] = ways % MOD;
        }
    vector<vector<ll>> R(S, vector<ll>(S, 0));
    for (ll i = 0; i < S; i++) R[i][i] = 1;
    for (; w; w >>= 1) {
        if (w & 1) R = mul(R, T);
        T = mul(T, T);
    }
    cout << R[0][0] << '\n';
}
2 2

643⋅log⁡21012≈10764^3 \cdot \log_2 10^{12} \approx 10^7 phép nhân.

Khi đi thi

  • Lưới có một cạnh không quá 10 đến 12: mặt nạ trên cạnh ngắn. Theo từng ô khi chuyển tiếp phụ thuộc ô kề trước đó; theo hàng khi ràng buộc chỉ giữa hai hàng và có thể lọc trước các mặt nạ hợp lệ.
  • Viết rõ nghĩa của từng bit trước khi viết chuyển tiếp: bit nói về ô nào của biên, ở cột nào, ý nghĩa gì; phần lớn lỗi nằm ở chỗ bit đổi nghĩa khi biên dời.
  • Cạnh dài tới 101210^{12}: ma trận chuyển giữa hai cột và lũy thừa nhị phân; 26=642^6 = 64 trạng thái là thoải mái, 28=2562^8 = 256 đã nặng.

Tóm tắt

  • Biên giữa phần đã xử lý và chưa xử lý mã hóa bằng mặt nạ; theo từng ô, biên gãy tại ô đang xét, O(nm2n)O(nm2^n) cho lát domino.
  • Theo hàng: lọc mặt nạ tự hợp lệ, kiểm hai hàng bằng phép bit.
  • Ràng buộc hai hàng: giữ hai mặt nạ trong trạng thái.
  • Bit có thể mang thông tin khác như hướng của ô phía trên, cho bài tối ưu.
  • Bảng rất dài: ma trận chuyển giữa các cột, lũy thừa nhị phân.

Bài tập

1
Khối Chữ L Ba Ôcanuc80k4401Bảng 2 hàng: trạng thái là mặt nạ 2 bit của cột hiện tại đã bị chiếm, mỗi cột thử bốn hướng của mảnh chữ L nằm trên cột đó và cột sau.
LQDOJ
2
Không Có Khối Vuông Đặccanuc80k4402Theo từng ô với biên dài M+1M + 1: cần biết ô trên, ô trên trái và ô trái để cấm hình vuông 2×22 \times 2 đầy; xoay bảng cho cạnh ngắn làm biên.
LQDOJ
3
Xếp Chỗ Trong Phòng Thicanuc80k4404Theo hàng với ghế hỏng: mặt nạ không hai bit kề, hai hàng liền nhau không được lệch chéo, tức (s << 1) & t và (s >> 1) & t đều bằng 0.
LQDOJ
4
Ma Trận Ô Vuông Lẻcanuc80k4406n≥4n \ge 4 thì luôn có hình vuông 4×44 \times 4 gồm bốn ô 2×22 \times 2 lẻ, tổng chẵn, vô nghiệm; n≤3n \le 3 thì mặt nạ cột, hai cột liền nhau phải cho mọi hình vuông 2×22 \times 2 lẻ.
LQDOJ
5
Không Còn Ô Cô Lậpcanuc80k4407Trạng thái là hàng trước và hàng hiện tại có bị đảo không; một ô ở hàng i−1i - 1 được kiểm khi đã biết hàng i−2i - 2, i−1i - 1, ii.
LQDOJ
6
Hành Lang Hai Phòngcanuc80k4408Theo tầng, trạng thái là (số phòng đã đóng, phòng nào đóng ở tầng trước); không được đóng hai phòng chéo nhau ở hai tầng liền nhau.
LQDOJ
7
Ghép Đôi Các Ô Chất Lượngcanuc80k4410Theo từng ô, bit là ô trên biên đã được ghép chưa; thử để trống, ghép ngang với ô phải, ghép dọc với ô dưới, cộng điểm theo bảng.
LQDOJ
8
Chọn Các Ô Không Kề Nhaucanuc80k4412Theo từng ô với biên W+1W + 1 bit để biết cả ô trên trái, trên, trên phải và trái; cạnh ngắn làm biên.
LQDOJ
9
Dọn Sạch Bảng Bốn Hàngcanuc80k4415Chỉ 4 hàng: mặt nạ là các ô * chưa dọn trong cửa sổ 4 cột tới; tại mỗi cột thử đặt các hình vuông bắt đầu ở cột đó.
LQDOJ
10
Khoảng Trống Sau Một Nhịpcanuc80k4420Số ô có quân cuối cùng ít nhất là một tập thống trị nhỏ nhất; quy hoạch động theo hàng với mặt nạ hàng trước, hàng hiện tại, mỗi ô phải được chính nó hay ô kề chọn.
LQDOJ
11
Đường Đi Qua Các Thanh Ngangcanuc80k4426Mỗi tầng là một mặt nạ thanh ngang không hai thanh kề nhau; trạng thái là vị trí hiện tại, nhân với số mặt nạ đưa vị trí sang trái, phải hay đứng yên.
LQDOJ
12
Tô Màu Các Miếng Dominocanuc80k4428Đi theo các domino từ trái sang: dọc nối dọc nhân 2, ngang nối ngang nhân 3, đổi loại nhân 1 hoặc 2; đầu bảng 3 hoặc 6.
LQDOJ
13
Lát Bảng Ba Hàngcanuc80k4435Bảng 3×n3 \times n: chỉ nn chẵn mới lát được, fn=4fn−2−fn−4f_n = 4 f_{n-2} - f_{n-4}, hoặc chạy biên gãy với nn tới 30.
LQDOJ
14
Các Vòng Đi Qua Vườn Câycanuc80k4449Phủ ô sàn bằng các chu trình: biên gãy kiểu “cắm”, mỗi bit cho biết cạnh từ ô đã xét đi vào biên có tồn tại không; mỗi ô sàn phải có đúng hai cạnh.
LQDOJ

Đọc thêm