AcWing 327. 玉米田(状态压缩DP)
- 一、问题
- 二、分析
- 1、思路
- 2、状态表示
- 3、状态转移
- 4、循环设计
- 5、初末状态
- 三、代码
一、问题
二、分析
1、思路
这道题与之前所讲解的AcWing 1064. 小国王(状态压缩DP)非常相似,所以如果大家没有思路的话,可以去看看之前的这篇文章,然后再回来做这道题。
这道题的思路也是一样,一行一行的看。至于这种思路为什么是正确的,作者在AcWing 1064. 小国王(状态压缩DP)里面进行过详细地讲解,这里不做过多的介绍了。
第 i i i行能种植玉米的方式取决于第 i − 1 i-1 i−1行。我们可以利用这个方式来书写转移方程。
这一题的变化在于题目中对土地也有了限制,图中写着1的是能种植,写着0的是不能种植。
我们先来分析一下合法状态需要满足的条件:
我们先不考虑土地的限制。
现在我们考虑一下土地,土地限制的是本行。按照题目所说1是能种植,0是不能种植。但是这样不太好进行后续的判断,现在我们每一行的土地所代表的01进行取反。这样的话,原来是1的土地现在是0,原来是0的土地现在是1。所以在原来是1的地方种植,即在取反后为0的地方种植。这样的话,就会呈现出下面的现象:对于取反后的土地,0能种植,1不能种植。这样的话,就必定满足我们的种植方案和本行的土地情况做&运算得0。
因此,可以将合法状态总结为下面的方式:
2、状态表示
f [ i ] [ s ] f[i][s] f[i][s]表示在前i行里面种植玉米,并且第i行的状态是s时,所有的种植方案。
3、状态转移
f [ i ] [ s ] = ∑ f [ i − 1 ] [ s s ] f[i][s]=\sum f[i-1][ss] f[i][s]=∑f[i−1][ss]
4、循环设计
最外层肯定是循环i,第二层就循环我们的状态ss,然后再去枚举第i-1层的可能合法状态进行判断。
这样是3层循环,那么最大的计算次数是:
12
∗
2
1
2
∗
2
1
2
12*2^12*2^12
12∗212∗212。由于我们会对状态进行预处理,仅保留二进制表示下没有相邻个1的状态,所以运算次数肯定比这个小。就算是这个运算次数,也没有到达上限,所以我们的方法是不超时的。
5、初末状态
f[0][0]=1即可。即再前0行种植,且种了0个的方案数是1。
最后的状态是f[n+1][0]。
即再前n+1行中种玉米,但是第n+1行不种玉米的方案数。
三、代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=15,S=1<<12,mod=1e8;
long long g[N],f[N][S];
vector<int>state;
int n,m;
bool check(int x)
{
for(int i=0;i+1<m;i++)
{
if((x>>i&1)&&(x>>(i+1)&1))return false;
}
return true;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
int x;
cin>>x;
g[i]+=(!x)*1<<j;
}
}
for(int i=0;i<1<<m;i++)
{
if(check(i))state.push_back(i);
}
f[0][0]=1;
for(int i=1;i<=n+1;i++)
{
for(int s=0;s<state.size();s++)
{
if(g[i]&state[s])continue;
for(int ss=0;ss<state.size();ss++)
{
if(g[i-1]&state[ss])continue;
if((state[s]&state[ss])==0)
f[i][state[s]]=(f[i][state[s]]+f[i-1][state[ss]])%mod;
}
}
}
cout<<f[n+1][0]<<endl;
return 0;
}