插头dp

参考:插头dp从入门到不会

img

插头dp是一种类似于状压dp的,用一维状态记录轮廓线上状态来解决格点图上问题的方式

Mondriaan’s Dream

求用$1\times 2$或$2\times 1$的骨牌覆盖$m\times n$棋盘的方案数

用一维记录轮廓线下方的格子是否已被覆盖

上一个状态只需要考虑当前格子是否已被填上,若已被填上则不能再填,轮廓线状态1->0,若未填,则有竖着填0->1和横着填00->01两种情况

#include <bits/stdc++.h>
#define debug(x) cerr << #x << " = " << x << ' '
#define debugl(x) cerr << #x << " = " << x << '\n'
#define mem(x, y) memset(x, y, sizeof(x))
#define all(x) x.begin(), x.end()

using namespace std;
typedef long long ll;

const int N = 1e5 + 5, MOD = 998244353;
const double eps = 1e-8;

int add(int a, int b) { return a + b >= MOD ? a + b - MOD : a + b; }
int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
int mul(int a, int b) { return 1ll * a * b % MOD; }
int bin(int x, int p) {
    int res = 1;
    for (int b = x; p; p >>= 1, b = mul(b, b))
        if (p & 1) res = mul(res, b);
    return res;
}

int h, w;

ll f[2][4000];

ll calc(int h, int w) {
    ll res = 0;
    int cur = 0;
    mem(f[cur ^ 1], 0);
    f[cur ^ 1][0] = 1;
    for (int i = 1; i <= h; i++) {
        for (int j = 1; j <= w; j++) {
            mem(f[cur], 0);
            for (int k = 0; k <= (1 << w) - 1; k++) {  
                if (!f[cur ^ 1][k]) continue;
                if ((k >> (j - 1)) & 1) f[cur][k ^ (1 << (j - 1))] += f[cur ^ 1][k];
                else {
                    if (j <= w - 1 && ((k >> (j - 1)) & 3) == 0) f[cur][k ^ (1 << j)] += f[cur ^ 1][k];
                    f[cur][k ^ (1 << (j - 1))] += f[cur ^ 1][k];
                }
            }
            cur ^= 1;
        }
    }
    return f[cur ^ 1][0];
}

int main() {
    while(scanf("%d %d", &h, &w), (h != 0 || w != 0)) {
        printf("%lld\n", calc(h, w));
    }
    return 0;
}

Eat the Trees

用若干回路覆盖n*m的棋盘,某些格子已被覆盖。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 12, M = (1 << 12) + 5, MOD = 998244353;

ll f[2][M], tf[M];

int a[N][N];

int mask;

int main() {
    int T, CNT = 0; scanf("%d", &T);
    while(T--) {
        int n, m; scanf("%d %d", &n, &m);
        mask = (1 << (m + 1)) - 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                scanf("%d", &a[i][j]);
            }
        }
        memset(f, 0, sizeof(f));
        int cur = 0;
        f[cur ^ 1][0] = 1; 
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                memset(f[cur], 0, sizeof(f[cur]));
                for (int k = 0; k <= (1 << (m + 1)) - 1; k++) {
                    if (!f[cur ^ 1][k]) continue;
                    int tmp = (k >> (j - 1)) & 3;
                    if (a[i][j] == 1) {
                        if (tmp == 0) f[cur][k ^ (3 << (j - 1))] += f[cur ^ 1][k];
                        if (tmp == 1) {
                            f[cur][k] += f[cur ^ 1][k];
                            f[cur][k ^ (3 << (j - 1))] += f[cur ^ 1][k];
                        }
                        if (tmp == 2) {
                            f[cur][k] += f[cur ^ 1][k];
                            f[cur][k ^ (3 << (j - 1))] += f[cur ^ 1][k];
                        }
                        if (tmp == 3) {
                            f[cur][k ^ (3 << (j - 1))] += f[cur ^ 1][k];
                        }
                    } else {
                        if (tmp == 0) f[cur][k] += f[cur ^ 1][k];
                    }
                }
                if (j == m) {
                    memset(tf, 0, sizeof(tf));
                    for (int k = 0; k <= (1 << m) - 1; k++) {
                        tf[(k << 1) & mask] += f[cur][k];
                    }
                    for (int k = 0; k <= (1 << (m + 1)) - 1; k++) {
                        f[cur][k] = tf[k];
                    }
                }
                cur ^= 1;
            }
        }
        printf("Case %d: There are %lld ways to eat the trees.\n", ++CNT, f[cur ^ 1][0]);
    }
    return 0;
}