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;
}