今天来给大家分享一道状压dp的题目,注意一下里面的技巧性东西
题意:
农场主John新买了一块长方形的新牧场,这块牧场被划分成N行M列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。
遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。
John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)
输入格式
第一行:两个整数N和M,用空格隔开。
第2到第N+1行:每行包含M个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。
输出格式
一个整数,即牧场分配总方案数除以100,000,000的余数。
输入输出样例
输入 #1
2 3 1 1 1 0 1 0
输出 #1
9
分析,一看数据范围就不难想到这道题主要是考察状压dp了,状压dp的难点就是如何设立状态以及找状态转移方程,对于这道题来说我们可以一行一行地处理,于是我们可以定义dp[i][j]为第i行状态为j的合法方案数,这样我们最后只需要把dp[n][i]累加起来输出即可,题目中说保证种草的两块区域不能相邻,相邻分为上下相邻和左右相邻,由于我们是一行一行处理的,所以我们可以在进行动态转移之前就先把一行中有草地相邻的情况给排除掉,假如某一行的状态是i,那么 (i&(i>>1))=0 不就说明该方案没有两块土地是相邻的了么?至于上下相邻的情况,我们可以在动态转移的过程中处理,比如当前行状态是i,下一行状态是j,那么(i&j)=0不就说明上下没有两块土地是相邻的了么,那我们又该如何处理贫瘠土地不能种草这个条件呢?我们可以用一个F[i]预处理出来第i行的所有肥沃土地状态,如果我们进行第i行的状态转移时用的状态都是F[i]的子集不就可以满足没有贫瘠土地种草的情况了吗,具体实现见代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=13;
const int mod=1e8;
int dp[N][1<<N],st[1<<N],F[N],f[N][N];
//st[i]记录状态i是否有相邻的1出现,F[i]记录第i行贫瘠土地状态
int main()
{
int m,n;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&f[i][j]);
F[i]=(F[i]<<1)+f[i][j];//记录第i行贫瘠土地(非常nice)
}
for(int i=0;i<1<<m;i++)
if((i&(i>>1))==0) st[i]=1;//记录无相邻土地的合法状态
dp[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<1<<m;j++)//枚举当前状态
if(st[j]&&((j&F[i])==j))//当前土地状态是第i行土地状态的子集
for(int k=0;k<1<<m;k++)//枚举上一行土地状态
{
if(j&k) continue;//如果上下相邻就舍去
dp[i][j]+=dp[i-1][k];
dp[i][j]%=mod;
}
long long ans=0;
for(int i=0;i<1<<m;i++)
ans=(ans+dp[n][i])%mod;
printf("%lld",ans);
return 0;
}
通过这道题目我们应该学到的是如何满足题目中所要求的限制条件,当然,这道题我们还可以把合法状态具体到每一行,也就是说我们把贫瘠土地的情况一开始就考虑进去,这样我们进行状态转移时只需要判断当前行的状态是否合理即可。
状态转移的合法状态应该在一开始就预处理出来,这样就不容易犯逻辑上的错误。