题目描述
农夫约翰的土地由$M \times N$ 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
(注意:这里是上下左右边缘,不是两斜对角边缘)
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
输入格式
第$1$行包含两个整数$M$和$N$。
第$2 \dots M+1$行:每行包含$N$个整数$0$或$1$,用来描述整个土地的状况,$1$表示该块土地肥沃,$0$表示该块土地不育。
输出格式
输出总种植方法对$100000000$取模后的值。
数据范围
$$ 1 \le M,N \le 2 $$
输入样例:
2 3
1 1 1
0 1 0
输出样例:
9
算法解析
配合算法进阶课y总讲解视频使用更佳。
算法构造
经典的棋盘型状态压缩动态规划,我们可以按照之前Acwing上P1064小国王的思路,处理本题。
首先,我们需要明确,题目的要求:
- 统计方案数
- 有些土地不能种植
状态设计
首先,我们得明确状态是什么。
我们这个状态,肯定是要统计方案数。
我们这个状态,必然需要表示每一行土地种植的状态。
因此得到:
$$
f[i][s]表示已经种植前i行,且第i行种植的状态为s的方案数
$$
状态转移
题目的限制条件,其实就是我们转移的限制条件。
我们知道,这里是十字形的禁止种植,也就是上下左右不能有相邻的两棵玉米。
那么怎么判断呢?
如果说我们把$1$表示这个地方种植玉米,$0$表示不种植
$$
S=1110 \quad 1,2,3这三个地方种玉米,第四个地方不种植玉米
$$
对于一行而言,不能种植相邻的玉米。
即:
对于一行而言,不能有相邻的$1$
$$
S=1110 \quad 是不合法的状态
$$
对于相邻的两行而言,不能在同一列都种植玉米
$$
a=1010 \\\\
b=1000 \\\\
这是不可以的,在第一个位置会出现上下矛盾
$$
那么我们可以转化为:
$$
a \& b==0
$$
最后,对于题目中的土地不能种植,我们可以认为。
$$
如果第i行的状态为s,那么荒废土地处不能有1
$$
我们可以设计一个数组:
$$
g[i]表示第i行不能种植土地的状态 \\\\
g[1]=1011 \quad 表示第一行,第一个,第三个,第四个位置不能种植玉米
$$
总而言之
$$
第i行的状态为s \\\\
那么s \& g[i]==0
$$
代码解析
#include <bits/stdc++.h>
using namespace std;
const int N=13,Mod=100000000;
vector<int> state,head[1<<N];
int n,m,x,f[14][1<<N],g[N];
inline bool check(int x)//快速判断有没有相邻的1
{
return !(x&x>>1);
}
inline void init()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
{
scanf("%d",&x);
g[i]+=(!x<<(j-1));//荒废土地是0,我们在这里转换为1
}
for(int i=0; i<(1<<m); i++)
if (check(i))//这个状态不存在种植左右相邻的玉米
state.push_back(i);
for(int i=0; i<state.size(); i++)
for(int j=0; j<state.size(); j++)
if (!(state[i] & state[j]))//i对应的状态和j对应的状态没有在同一列种植玉米
head[i].push_back(j);
f[0][0]=1;
for(int i=1; i<=n+1; i++)
for(int a=0; a<state.size(); a++)
{
if (state[a] & g[i])//在第i行,状态a是否满足在荒废土地没有种植玉米
continue;
for(int b=0; b<head[a].size(); b++)//从上一行b对应的状态,转到本行a对应的状态
f[i][a]=(f[i][a]+f[i-1][head[a][b]])%Mod;
}
printf("%d\n",f[n+1][0]);//表示第n+1行什么都没种植的状态,其实就是累加f[n][S]
}
signed main()
{
init();
return 0;
}
y总视频 配上秦大佬题解效果极佳%%%%%
你?
看完题解真通透啊,思路清晰,感觉难度在于把思路转抽象成代码,大佬太强了
666666
厉害了 通俗易懂
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
$ $
通俗易懂
大佬,想问一下,为什么用state[a]表示状态,最后更新数组却用f[i][a],a不是数组下标吗,怎么是状态
其实都可以的,两种都能过,用a数组下标能过是因为,每个state[a]与下标a是一一对应的,因此也可以。
不能啊,刚试了一下, 样例答案是7
不能啊,刚试了一下, 样例答案是7
我有个问题为什么在判断 “ //从上一行b对应的状态,转到本行a对应的状态 ”不需要判断是否与上一行的地形符合
就是为什么不在判断一下if(head[a][b]&g[i-1])==0)
m,n的范围给错了,应该是小于等于12
f[0][0]=1; 这第二个0表示的是状态,而转移方程f[i][a]=(f[i][a]+f[i-1][head[a][b]])%Mod;这里的时候head[a][b]存的是状态的下标,这怎么转移呀?能转移的话,f[0][0]第二个0不也应该表示为状态的下标吗?
我也在纠结这个 但是实际上 下标和二进制数之间是一 一对应的 所以没问题
相当于给二进制数进行了离散化 节省了空间
y总视频 配上秦大佬题解效果极佳%%%%%
y总视频 配上秦大佬题解效果极佳%%%%%
y总视频 配上秦大佬题解效果极佳%%%%%
QwQ
y总视频 配上秦大佬题解效果极佳%%%%%
QwQ