寒假Day54:POJ3254-Corn Fields-状压dp入门题
题意:
给出n、m,接下去给出n行m列的状态(只有0或1),
只能在1的位置种植,并且如果这块地方种植了,那么其上下左右,也就是相邻部分不可种植,
问:总共有多少种种草方案。
思路:状压dp入门
状压dp求解什么样的问题?
数据范围比较小;简单算法无法解决。
一般动态规划无法解决,因为一般的dp转移方程只能从一边过来,
这一题需要上下左右都要考虑到。
本质:利用位运算记录状态,从而实现dp
本题的状态划分:每一行作为一个状态,
从而达到:第i行的状态从 i - 1 行转移,并且只从它转移。
也就是说,第 i 行的状态只取决于第 i - 1 行。
如何判断上一行状态和现在这一行状态是否冲突?
利用与运算,1&1=1,1&0=0,0&1=0,0&0=0,
如果种植起冲突的话,与运算结果为1,所以为0的时候可以种植啦。
如何判断当前位置/当前状态可行?
上下判断:是0表示可以种植并;左右判断:左右为0
将该行所代表的二进制数101010这样的形式左移或者右移,再和上面的步骤一样,利用与运算进行判断即可。
for(int i=0; i<(1<<m); i++) //总状态数 { int z=(i&(i<<1))==0;//左移判断该状态是否满足 int y=((i&(i>>1))==0);//右移... book[i]=(z&&y); //or book[i]=((i&(i<<1))==0)&&((i&(i>>1))==0); }
表示每一行的状态:
因为只有0、1,所以可以考虑从二进制入手;
把每一行看作是一个二进制数,则这个数就代表这一行的状态。
dp[i][j]:代表第 i 行的状态是 j。
注意:由于符号优先级问题,不确定优先级的请一律加上括号
AC代码:(代码里有具体解析)
#include<stdio.h> #include<iostream> #include<string.h> #include<vector> using namespace std; const int N=15; const int mod=100000000; int n,m,a[N][N],dp[N][1<<N]; int h[N];//每一行空地的状态 int book[1<<N];//标记每个状态是否满足要求 int main() { cin>>n>>m; for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { cin>>a[i][j]; h[i]=(h[i]<<1)+a[i][j];//处理每一行状态,变成二进制状态,0也要处理 } } //左右判断 for(int i=0; i<(1<<m); i++) //总状态数 { int z=(i&(i<<1))==0;//左移判断该状态是否满足 int y=((i&(i>>1))==0);//右移... book[i]=(z&&y); //or book[i]=((i&(i<<1))==0)&&((i&(i>>1))==0); } dp[0][0]=1; for(int i=1; i<=n; i++) { for(int j=0; j<(1<<m); j++)//枚举第i行的每一个状态(列) { if(book[j]&&(j&h[i])==j)//状态可行and该位置是0可以种植 { for(int k=0; k<(1<<m); k++)//不是j,枚举上一行的状态 { if((k&j)==0)//不冲突 dp[i][j]=(dp[i][j]+dp[i-1][k])%mod; } } } } int ans=0; for(int i=0; i<(1<<m); i++) ans=(ans+dp[n][i])%mod;//答案加起来 cout<<ans<<endl; return 0; }