AcWing 327 玉米田

\(AcWing\) \(327\). 玉米田

一、题目描述

农夫约翰的土地由 \(M×N\) 个小方格组成,现在他要在土地里种植玉米。

非常遗憾,部分土地是 不育 的,无法种植。

而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘

现在给定土地的大小,请你求出共有多少种种植方法

土地上什么都不种也算一种方法

输入格式
\(1\) 行包含两个整数 \(M\)\(N\)

\(2..M+1\) 行:每行包含 \(N\) 个整数 \(0\)\(1\),用来描述整个土地的状况,\(1\) 表示该块土地肥沃,\(0\) 表示该块土地不育。

输出格式
输出总种植方法对 \(10^8\) 取模后的值。

数据范围
\(1≤M,N≤12\)

输入样例

2 3
1 1 1
0 1 0

输出样例

9

二、样例数据解析

原始数据

部分土地是不育的,无法种植。
\(1\)是可以摆放的,\(0\)是不可以摆放的。

1 1 1
0 1 0

相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。

可能的摆放办法

三、棋盘类状态压缩\(DP\)套路

  • 是否存在连续\(1\)
//是不是存在连续1
bool check(int x) {
    return !(x & x >> 1);//存在连续1,就是不合法,返回false,否则返回true
}
  • 预处理出所有有效状态【没有连续数字1的状态就是有效状态】
for (int i = 0; i < 1 << m; i++)   // 0~2^m-1,相当于遍历二进制的每一种可能性
  if (check(i)) st.push_back(i);   //棋盘类,用check检查状态的合法性,记录合法状态,不允许有连续的数字1
  • 使用一维数组压缩存储不允许种田的位置
 for (int i = 1; i <= n; i++) // n行
        for (int j = 0; j < m; j++) {//m列            
            int x;
            scanf("%d", &x);            
            if (x == 0) g[i] |= 1 << j;
        }

\(Q\):为什么\(g[i]\)要记录 无法摆放的位置,而不记录 可以摆放的位置 呢?
: 因为这样设计可以 快速判断是不是上下行兼容
思考路径:

  • ① 不考虑原始地图中不育的情况下,单纯讨论是不是本行无连续\(1\),上下行是不是存在同列\(1\),这两个用check(x),a|b==0就可以了~
  • ② 只有上面的考虑怕是还不行,还需要考虑 原始地图 与 当前枚举到的状态 是否兼容,也就是你想往某个位置上种玉米,结果那个位置上不育,需要\(continue\)
  • ③ 怎么判断呢?用 位运算
    \(g[i]\)表示原始地图状态,\(g[i]\)有两种设计思路:
    • \(1\)可以放,\(0\)不可以放
    • \(0\)可以放,\(1\)不可以放

这两种设计用哪个呢?当然是哪个方便用哪个!

  • 如果选择(\(1\))方式
    假设有一个场景:\(2\)个位置是肥沃的,可以放的,现要让我们检查一个状态是不是合法,此状态要求往\(3\)个位置放玉米,此时,无论我们用&,还是用|,都是无法准确检测此状态的非法性的!
  • 如果选择(\(2\))方式
    只要g[i] & a > 0, 现实含义:想放不让放,表示冲突

总结:正难则反,变换思路

  • 预处理合法状态之间的兼容关系
for (int a : st)
    for (int b : st)
        //此步骤只处理两行之间没有竖着的冲突就算合理转化,不考虑无法耕种情况
        if ((a & b) == 0) head[a].push_back(b);
  • 算法步骤:
    一层:枚举每一行
    二层:枚举每一种合法状态,如果存在不能摆放的状态,需要做一下与运算\(continue\)
    三层:枚举每种状态的兼容前序状态
    尝试进行状态上的转化,合理设计状态转移方程

  • 枚举最后一行的所有可能状态,累加出答案

四、实现代码

#include <bits/stdc++.h>

using namespace std;
const int MOD = 1e8;   // 按1e8取模
const int N = 14;      // M*N个小方格,上限都是12,这里我们故意取大一点,到14.
const int M = 1 << 12; // 0~2^12-1,共2^12个状态,记录每一行的摆放状态
int n, m;              // n行,m列
int g[N];              // 记录哪个位置是无法种田的,在题目中,输入的位置是0表示无法种田
vector<int> st;        // 合法状态
vector<int> head[M];   // 一个状态可以转化为哪些状态(与哪此状态兼容)
int f[N][M];           // 一维:完成了第i行,二维:在状态是j(二进制状态)的情况下,值:方案数量

// 状态检查是否合法,某个状态是不是存在连续1
bool check(int x) {
    return !(x & x >> 1);
}

int main() {
    // 1、输入地图
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)       // n行
        for (int j = 1; j <= m; j++) { // m列
            int x;
            scanf("%d", &x);
            if (x == 0)               // 当前位置是不育的玉米地
                g[i] |= 1 << (j - 1); // 在对应的二进制位上标识为1,这里g[i]存放的是十进制,所以将增加的二进制转为十进制加入g[i]
        }

    // 2、预处理出所有合法状态
    for (int i = 0; i < 1 << m; i++)
        if (check(i)) st.push_back(i);

    // 3、预处理出每个合法状态兼容哪些状态
    for (int a : st)
        for (int b : st)
            if ((a & b) == 0) head[a].push_back(b);

    // 4、开始DP
    f[0][0] = 1;                 // 已经放完第0行,状态是一个也没有放,算一种方案
    for (int i = 1; i <= n; i++) // 枚举每一行
        for (int a : st) {       // 枚举每一个合法状态
            // 如果当前枚举出的a说某个位置需要种植玉米,而g[i]记录的此位置不育,那么就不能在此位置耕种
            if ((g[i] & a)) continue;
            // 到这里时,表明此状态完全合法,找它的所有兼容状态,所有兼容状态都可以顺利转移到a状态
            for (int b : head[a])
                f[i][a] = (f[i][a] % MOD + f[i - 1][b] % MOD) % MOD;
        }

    // 结果
    int res = 0;
    for (int a : st) res = (res % MOD + f[n][a] % MOD) % MOD;
    printf("%d", res);
    return 0;
}
posted @ 2022-01-01 12:51  糖豆爸爸  阅读(193)  评论(0编辑  收藏  举报
Live2D