HDU 4804 Campus Design 轮廓线DP

题意:给出一个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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值