题意
给一个网格,里面能放1*1和1*2的方格,标记为0的地方不能放方格。问有多少种摆放方式。
题解
刘汝佳的训练指南上有这个的简化版本,在哪个版本中不存在1*1的方格和不能放置的方格。但是实际上,这种放方格的问题,无论有多少要求,轮廓线DP都能很好的解决。
深入理解轮廓线DP是解决这道题的前提条件,普通的状压DP是针对行来考虑的,而轮廓线DP是针对格子来考虑的。对行进行的考虑,只能依靠枚举两行的状态来判断是否可以进行状态转移,对于一些比较复杂的要求,根本无法实现。但是轮廓线DP就不一样了,由于操作单元是格,因此可以实现一些比较复杂的DP问题。
对于这道题,我们在进行DP的时候首先要考虑格子是否能放置方格,对于不能放置方格的情况,我们直接假设这个地方已经放置了一个1*1的方格。对于可以放置方格的情况,我们考虑四种情况,1*1,1*2横放,1*2竖放,不放。我们可以将更新操作提取成一个函数,这样的话对于取模操作可以比较方便地去实现。
代码
#include<bits/stdc++.h>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define W(t) while(t)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define eps 1e-10
#define MAXN 3000010
#define MOD 1000000007
#define COUT(x) cout<<x<<endl
using namespace std;
char mp[110][12];
LL dp[2][1100][25];
int pre,now;
int n,m,c,d;
void update(int a,int k,int p,int add){
if(k&(1<<m)){
dp[now][k^(1<<m)][p+add]=(dp[now][k^(1<<m)][p+add]+dp[pre][a][p])%MOD;
}
}
int main() {
W(~scanf("%d%d%d%d",&n,&m,&c,&d)) {
UP(i,0,n) {
scanf("%s",mp[i]);
}
MEM(dp,0);
pre=0,now=1;
dp[now][(1<<m)-1][0]=1;
UP(i,0,n){
UP(j,0,m){
swap(now,pre);
MEM(dp[now],0);
UP(k,0,(1<<m)){
UP(p,0,d+1){
if(mp[i][j]-'0'!=0){
update(k,k<<1,p,0); //no
update(k,(k<<1)|1,p,1); //one
if(j&&!(k&1)) update(k,(k<<1)|3,p,0); //left
if(!(k&(1<<(m-1)))) update(k,(k<<1)|(1<<m)|1,p,0);//up
}else{
update(k,(k<<1)|1,p,0);
}
}
}
}
}
LL ans=0;
UP(p,c,d+1){
ans=(ans+dp[now][(1<<m)-1][p])%MOD;
// COUT(dp[now][(1<<m)-1][p]);
}
printf("%I64d\n",ans);
}
}