一、样例数据解析
原始数据
可能的摆放办法
二、棋盘类状态压缩DP套路总结
- 使用一维数组压缩存储不允许种田的位置
for (int i = 1; i <= n; i++) //n行
for (int j = 1; j <= m; j++) { //m列
int t;
cin >> t;
//状态压缩,记录每一行的状态
//1、这里只记录不能耕种,即t==0的情况,可以耕种的不用记录啦~
//2、按二进制转十进制的思想进行记录每一位,从左到右,每次左移一位解决数位问题,理解后需要背诵下来
if (!t)g[i] += 1 << (j - 1);
}
-
预处理所有合法状态
-
预处理合法状态之间的兼容关系
-
三层循环,枚举每一行,枚举每一种状态,枚举每种状态的兼容前序状态,在合乎题意的前提下,尝试进行状态上的转化。
-
枚举最后一行的所有可能状态,累加出答案。
三、朴素版本代码
#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、输入地图
cin >> n >> m;
for (int i = 1; i <= n; i++) //n行
for (int j = 1; j <= m; j++) { //m列
int t;
cin >> t;
//状态压缩,记录每一行的状态
//1、这里只记录不能耕种,即t==0的情况,可以耕种的不用记录啦~
//2、按二进制转十进制的思想进行记录每一位,从左到右,每次左移一位解决数位问题,理解后需要背诵下来
if (!t)g[i] += 1 << (j - 1);
}
//2、预处理,哪些状态是合法状态
for (int i = 0; i < 1 << m; i++) //0~ pow(2,m)-1,相当于遍历二进制的每一种可能性
if (check(i)) st.push_back(i);//棋盘类,用check检查状态的合法性,记录合法状态
//3、预处理,每个合法状态可以兼容哪些状态
for (int a: st)
for (int b: st)
//此步骤只处理两行之间没有竖着的冲突就算合理转化,不考虑无法耕种情况
if ((a & b) == 0) head[a].push_back(b);
//4、开始DP
f[0][0] = 1;//啥也不放算一种方案
for (int i = 1; i <= n; i++) //枚举每一行
for (int a: st) { //枚举每一个合法状态
if ((g[i] & a)) continue;
//不能耕种的土地记录在g[i]里,某位为1表示此列不能耕种
//st[j]的含义是某种合法状态
//其它情况都是随意种
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;
}
四、滚动数组优化版本代码
#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[2][M]; //一维:完成了第i行,二维:在状态是j(二进制状态)的情况下,值:方案数量
//状态检查是否合法,某个状态是不是存在连续1
bool check(int x) {
return !(x & x >> 1);
}
int main() {
//1、输入地图
cin >> n >> m;
for (int i = 1; i <= n; i++) //n行
for (int j = 1; j <= m; j++) { //m列
int t;
cin >> t;
//状态压缩,记录每一行的状态
//1、这里只记录不能耕种,即t==0的情况,可以耕种的不用记录啦~
//2、按二进制转十进制的思想进行记录每一位,从左到右,每次左移一位解决数位问题,理解后需要背诵下来
if (!t)g[i] += 1 << (j - 1);
}
//2、预处理,哪些状态是合法状态
for (int i = 0; i < 1 << m; i++) //0~ pow(2,m)-1,相当于遍历二进制的每一种可能性
if (check(i)) st.push_back(i);//棋盘类,用check检查状态的合法性,记录合法状态
//3、预处理,每个合法状态可以兼容哪些状态
for (int a: st)
for (int b: st)
//此步骤只处理两行之间没有竖着的冲突就算合理转化,不考虑无法耕种情况
if ((a & b) == 0) head[a].push_back(b);
//4、开始DP
f[0][0] = 1;//啥也不放算一种方案
for (int i = 1; i <= n; i++) //枚举每一行
for (int a: st) { //枚举每一个合法状态
f[i & 1][a] = 0; //清零
if ((g[i] & a)) continue;
//不能耕种的土地记录在g[i]里,某位为1表示此列不能耕种
//st[j]的含义是某种合法状态
//其它情况都是随意种
for (int b: head[a])
//如果两者 & 大于零,表示存在某位或某几位,不允许种,但偏要种,这是不行的
f[i & 1][a] = (f[i & 1][a] % MOD + f[i - 1 & 1][b] % MOD) % MOD;
}
//结果
int res = 0;
for (int a: st) res = (res % MOD + f[n & 1][a] % MOD) % MOD;
printf("%d", res);
return 0;
}