[Acwing 327] 玉米田 题解

刚学状压 DP。第一题并不是互不侵犯,也不是旅行商问题,而是这一题。

题目描述

给你一个 \(N\times M\) 的 01 矩阵,你需要在上面摆放棋子。该矩阵 0 位上不能摆放棋子,在 1 位上可以摆放,且任意两个摆放的棋子所占的位不能有公共边。求问有多少摆放方式。

特别地,不放棋子也算一种方式。答案对 \(10^8\) 取模。

数据范围:\(1\le N,M\le 10\)

解法

看到数据范围很小,考虑状压 DP。

这题很不毒瘤,我们可以发现棋子是四联通的,而且每层的状态可以用一个二进制数来表示,原矩阵也可以用 \(N\) 个二进制数来存储。这样每一层的状态就被压缩成了 DP 的一维。

分析可得以下信息:

  • DP 状态集合:\(f_{i,j}\) 表示在前 \(i\) 层的影响下,对于第 \(i\) 层状态为 \(j\) 的方案。
  • DP 状态意义:\(f_{i,j}\) 表示在该意义下方案的种数。
  • DP 状态转移:让我们定义 \(pre\) 为我们枚举的上一行的可以使 \(j\) 这个状态合法的状态。则有 \(f_{i,j} = \sum\limits^{}_{pre}f_{i - 1, pre}\)

代码实现

按照上面的方式 DP 即可,但是有一个处理到 \(n+1\) 行的小 trick。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 15;
const int maxm = 1 << 12;
const int mod = 1e8;

int n, m, g[maxn];
int f[maxn][maxm];
vector<int> state;
vector<int> head[maxm];

bool check(int state) {
    return !(state & state << 1);
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) 
        for(int j = 0, k; j < m; j ++) 
            cin >> k, g[i] |= !k << j;
    for(int i = 0; i < 1 << m; i ++) 
        if(check(i))
            state.push_back(i);
    for(auto st : state) 
        for(auto nxt : state) 
            if (!(st & nxt))
                head[st].push_back(nxt);
    f[0][0] = 1;
    for(int i = 1; i <= n + 1; i ++) 
        for(auto st : state) 
            if (!(st & g[i]))
                for(auto pre : head[st])
                    f[i][st] = (f[i][st] + f[i - 1][pre]) % mod;
    cout << f[n + 1][0];
}
posted @ 2021-08-04 20:16  Inversentropir-36  阅读(36)  评论(0编辑  收藏  举报