什么是状压DP:
状压DP就是一种能够把当前状态如用一个二进制数表示出来后作为动态规划的其中一个参数的DP方式。其实动态规划的本质就是由于题目中的某一些情况与之前的情况有所联系,而这个联系就是他们之间的动态转移方程。而用到状压DP的题目中的状态一般都是像这个玉米田这个题目中的一样连续一排中有些格子有玉米,有些格子没有玉米,这样导致我们可以使用一个二进制数来表示这一行玉米的种植情况,我们用1来表示这个格子上种了玉米,用0来表示这个格子上没有种玉米,假如我们这么种玉米:
那么我们就可以用一个二进制数来表示这一行玉米的种植情况: 1010
,在将这个二进制数转换为十进制数,得到一个十进制数x。这样我们就可以通过数x方便地表示这一行玉米的种植情况了。
之后我们用 $ f_{i,j} $ 来表示第\(i\) 行种植情况为 \(j\)的时候的方案数,之后我们就可以 发现 得到动态转移方程:
\[ f_{i,j}=\sum f_{i,k} (k为上一层的所有情况)\]
这样我们就可以从上一层的所有情况中递推了。
几个题目中需要用到的小技巧:
我们在这个题目中需要判断同一行中的种草的地方是不是相邻的,可以用这个东西判断
for(int i=0;i<k;i++)
{
if(((i&(i<<1))==0) && ((i&(i>>1))==0))
{
check[i]=1;
}
}
如果当前状态的转换为十进制后为$ i $,那么当check[i]==1
的时候说明同一行种草的地方是不会有相邻的。
如果要判断这个地方中行与行间是不是有相邻的种草的地方,也可以用类似的方法,只需要判断上一个状态数 \(g\) 与当前状态数 \(j\)进行 AND运算后是不是会pow(2,n)-1
就可以了
最后一个要注意的地方就是由于状压dp的下标值很大,所以记得开大数组
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 15;
const int mod = 1e8;
int m,n;
int p[N],check[5000];
int tmp[N][5000];
int map[N][N];
int main()
{
scanf("%d %d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
scanf("%d",&map[i][j]);
//储存每个农田状态
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
p[i]=(p[i]<<1)+map[i][j];
}
}
int k=1<<n;
//用来判断一个数转换成二进制数后是不是形如10101010或01010101的
for(int i=0;i<k;i++)
{
if(((i&(i<<1))==0) && ((i&(i>>1))==0))
{
check[i]=1;
}
}
tmp[0][0]=1;
for(int i=1;i<=m;i++)
{
for(int j=0;j<k;j++)
{
if(check[j] && ((j&p[i])==j))
{
for(int u=0;u<k;u++)
{
if((u&j)==0)
{
tmp[i][j]=(tmp[i][j]+tmp[i-1][u])%mod;
}
}
}
}
}
int sum=0;
for(int i=0;i<k;i++)
{
sum+=tmp[m][i];
sum%=mod;
}
printf("%d\n",sum);
return 0;
}