题意:给出一个N*M的校园,用1*1的砖和1*2的砖将其铺满。校园中有一些地方是不能铺砖的。且,使用1*1的砖的数量有限制。求在限制的情况下,完全铺满的方案数。
思路:为了有序化,我们从左向右,从上到下铺砖。
对于当前考虑的格(i,j),我们用状态压缩,保存所有没有被放置的格子的最上面一个格子的状态。0表示没有被覆盖,1表示已经被覆盖。或者叫没有被覆盖区域的轮廓线。
这样,我们就可以递推了。
1。不能放砖时,当且仅当该格子的上一行的格子被覆盖,才能转移到下一个状态(保证到下一个状态时,已经考虑完的格子被完全覆盖)。
可以放砖时:
2.1*1的砖没有放到上限,可以转移,同时覆盖状态不变。
3.横放1*2的砖:当当前考虑格子的左面一个格子没有被覆盖,才能放,状态转移,覆盖状态需要使左面的格子从没有被覆盖变成被覆盖。
需要注意的是,上面两种转移都是在同一行,能进行上面转移的必要条件,也是考虑格子的上一行的格子被覆盖。
4.竖放1*2的砖:当当前考虑的格子的上一行的格子没有被覆盖,才能转移,同时覆盖状态为:该格子从没有被覆盖变成被覆盖。
5.不放。直接转移,同时,覆盖状态从被覆盖变成没有被覆盖。
4.5操作可以直接用一个异或操作变成一个语句。
最后只需统计在给定1*1砖的数量范围下的方案数即可。
注意:因为在递推的中间,用循环数组,需要重复的memset操作,如果数组开的太大,会超时。
代码如下:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 110;
const int MAXM = 11;
const int MAXC = 21;
const int MOD = 1e9 + 7;
int N,M,C,D;
char color[MAXN][MAXM];
int dp[2][MAXC][1<<MAXM];
int now,pre;
int main(void)
{
//freopen("input.txt","r",stdin);
while(scanf("%d %d %d %d", &N,&M, &C, &D) != EOF){
for(int i = 0; i < N; ++i)
scanf("%s",color[i]);
int all = (1<<M) - 1;
now = 0, pre = 1;
memset(dp[now],0,sizeof(dp[now]));
dp[now][0][all] = 1;//初始化
for(int i = 0; i < N; ++i){
for(int j = 0; j < M; ++j){
swap(pre,now);
memset(dp[now],0,sizeof(dp[now]));
for(int k = 0; k <= D; ++k){
for(int s = 0; s <= all; ++s){
if(dp[pre][k][s]){
if(color[i][j] == '1'){
if(s & 1 << j){// 横着放,当前格子的上方的格子必须被覆盖
if(k < D)//放1*1的砖,前后状态不变
dp[now][k+1][s] = (dp[now][k+1][s] + dp[pre][k][s]) % MOD;
if(j && !(s & 1 << (j-1)))//左面有没有被覆盖的格子,放1*2的砖
dp[now][k][s | 1 << (j - 1)] = (dp[now][k][s | 1 << (j - 1)] + dp[pre][k][s]) % MOD;
}
//竖着放或者不放
//当上方格子被覆盖时,不能再放砖。当上方格子没有被覆盖时,能放1*2的砖。可以合并为一个异或
dp[now][k][s ^ 1 << j] = (dp[now][k][s ^ 1 << j] + dp[pre][k][s]) % MOD;
}
else if(s & 1 << j)
dp[now][k][s] = (dp[now][k][s] + dp[pre][k][s]) % MOD;
}
}
}
}
}
int ans = 0;
for(int i = C; i <= D; ++i)
ans = (ans + dp[now][i][all]) % MOD;
printf("%d\n",ans);
}
return 0;
}