You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 327. 玉米田

一、题目描述

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

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

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

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

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

输入格式1 行包含两个整数 MN

2..M+1 行:每行包含 N 个整数 01,用来描述整个土地的状况,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;
}