Files
python/TangDou/AcWing/StateCompression/327.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

175 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##[$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
```
<center><img src='https://img2022.cnblogs.com/blog/8562/202202/8562-20220208152754495-1214925478.png'></center>
> 相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
可能的摆放办法
![](https://img2020.cnblogs.com/blog/8562/202201/8562-20220101125401909-656540172.png)
### 三、棋盘类状态压缩$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 <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;
}
```