题目链接:
题目大意:
题目大意:农夫有一块地,被划分为m行n列大小相等的格子,其中一些格子是可以放牧的(用1标记),农夫可以在这些格子里放牛,其他格子则不能放牛(用0标记),并且要求不可以使相邻格子都有牛。现在输入数据给出这块地的大小及可否放牧的情况,求该农夫有多少种放牧方案可以选择(注意:任何格子都不放也是一种选择,不要忘记考虑!)
转大佬的思路:https://www.cnblogs.com/BlackStorm/p/4706243.html
对于0-1状态矩阵,自然而然会想到用状态压缩来做,把一行(也可以按列)的状态压缩成一个十进制数(行状态)。另种植or不种植也可以用0-1表示,并根据题目所说不能挨着种植,即这一行的某个位置种植了,下一行的同一位置就不能种植,可以知道两行的种植状态相位与要为0。
另外说一个行种植状态有效,即相邻的格子是不能种植的,需要左移一位后与自身相位与为0,如果存在相邻种植的格子,则一定会保留位1,不可能得出0的结果,据此枚举出这些状态再进行判断。
然而并不是每个有效的行种植状态对于每一行都有效,因为有的行存在一些位置是不能种植的,用0表示。为了方便判断和计算,我们考虑将行状态取反,即0表示肥沃,1表示不肥沃,这样只有当行种植状态和行状态相位与为0,这个种植状态才在该行有效,因为如果种在了不肥沃的格子上,相位与会保留位1,结果不为0。
于是我们有以下设计思路:
①枚举所有有效的种植状态,存到rec[]数组里,并将最大值存进去避免后面越界;
②在读入时就将格子状态取反,压缩成行状态存到row[]数组里;
③先处理第一行,给dp一个基准:对于每个有效种植状态,如果在第一行也有效,计数1次;
④对于剩余的行,不仅要判断每个有效种植状态,还要判断两行的种植状态有没有冲突;
⑤对于最后一行,把每个种植状态的计数加起来,就是总的种植方法数。
以及DP数组:dp[r][j]表示当第r行的种植状态为第j种状态时,现在玉米地的种植方案数。
状态转移方程: dp[r][j] = dp[r-1][i] + dp[r][j], if row[i-1]&rec[i]=0 and row[i]&rec[j]=0 and rec[i]&rec[j]=0.
即rec[i]是row[i-1]的有效行状态,且rec[j]是row[r]的有效行状态,且rec[i]和rec[j]两个行状态不发生冲突。
很重要的结论:
1:预处理满足没有相邻放牧的所有的行时:需要左移一位后与自身相位与为0
2:所有满足的可能行于现在这个行没有冲突的条件:当行种植状态和行状态相位与为0
3:于上一行的状态没有冲突:两行的种植状态相位与要为0。
位运算很重要!位运算很重要!位运算很重要!还有不要把&写成&&。
#include <bits/stdc++.h>
using namespace std;
const int mod=100000000;
int xx[377];//所有合法的行状态
int now[13];//目前输入的行状态
int dp[13][377];
int main()
{
int x=1<<12, k=0;
for(int i=0;i<x;i++)
{
if(!(i&(i<<1))) //行状态合法
{
xx[k++]=i;
}
}
xx[k]=x; //结束标志
int n, m, t;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%d",&t);
now[i]=(now[i]<<1)|!t;//保存(取反)的行状态
}
}
x=1<<m;//x目前的最大值
for(int i=0;xx[i]<x;i++)//预处理第一行状态
{
if(!(xx[i]&now[0]))
{
dp[0][i]=1;
}
}
for(int H=1;H<n;H++)
{
for(int i=0;xx[i]<x;i++)
{
if(!(xx[i]&now[H-1]))//枚举上一行可能的状态I
{
for(int j=0;xx[j]<x;j++)
{
if(!(xx[j]&now[H]))//枚举这一行可能的状态J
{
if(!(xx[i]&xx[j]))//如果I和J不冲突
{
dp[H][j]=(dp[H][j]+dp[H-1][i])%mod;
}
}
}
}
}
}
int ans=0;
for(int i=0;xx[i]<x;i++)//最后一行的状态和为答案
{
ans=(ans+dp[n-1][i])%mod;
}
printf("%d\n", ans);
return 0;
}