##[$AcWing$ $327$. 玉米田 ](https://www.acwing.com/problem/content/329/)
### 一、题目描述
农夫约翰的土地由 $M×N$ 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是 **不育** 的,无法种植。
而且,**相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘**。
现在给定土地的大小,**请你求出共有多少种种植方法**。
**土地上什么都不种也算一种方法**。
**输入格式**
第 $1$ 行包含两个整数 $M$ 和 $N$。
第 $2..M+1$ 行:每行包含 $N$ 个整数 $0$ 或 $1$,用来描述整个土地的状况,$1$ 表示该块土地肥沃,$0$ 表示该块土地不育。
**输出格式**
输出总种植方法对 $10^8$ 取模后的值。
**数据范围**
$1≤M,N≤12$
**输入样例**:
```cpp {.line-numbers}
2 3
1 1 1
0 1 0
```
**输出样例**:
```cpp {.line-numbers}
9
```
### 二、样例数据解析
原始数据
> 部分土地是不育的,无法种植。
> $1$是可以摆放的,$0$是不可以摆放的。
```c++
1 1 1
0 1 0
```
> 相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
可能的摆放办法

### 三、棋盘类状态压缩$DP$套路
- ① **是否存在连续$1$**
```cpp {.line-numbers}
//是不是存在连续1
bool check(int x) {
return !(x & x >> 1);//存在连续1,就是不合法,返回false,否则返回true
}
```
- ② **预处理出所有有效状态【没有连续数字1的状态就是有效状态】**
```cpp {.line-numbers}
for (int i = 0; i < 1 << m; i++) // 0~2^m-1,相当于遍历二进制的每一种可能性
if (check(i)) st.push_back(i); //棋盘类,用check检查状态的合法性,记录合法状态,不允许有连续的数字1
```
- ③ **使用一维数组压缩存储不允许种田的位置**
```cpp {.line-numbers}
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`, 现实含义:**想放不让放,表示冲突** 。
>
>**总结**:正难则反,变换思路
- ④ **预处理合法状态之间的兼容关系**
```cpp {.line-numbers}
for (int a : st)
for (int b : st)
//此步骤只处理两行之间没有竖着的冲突就算合理转化,不考虑无法耕种情况
if ((a & b) == 0) head[a].push_back(b);
```
- **算法步骤**:
一层:枚举每一行
二层:枚举每一种合法状态,如果存在不能摆放的状态,需要做一下与运算$continue$掉
三层:枚举每种状态的兼容前序状态
尝试进行状态上的转化,合理设计状态转移方程
- **枚举最后一行的所有可能状态,累加出答案**
### 四、实现代码
```cpp {.line-numbers}
#include
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 st; // 合法状态
vector 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;
}
```