hdu 4804 Campus Design

31 篇文章 0 订阅
5 篇文章 0 订阅

Problem

acm.hdu.edu.cn/showproblem.php?pid=4804
vjudge.net/contest/194949#problem/C(密码:sesefadou)

Meaning

n * m 的地板要铺满地砖,其中有一些格是有花的不能铺。砖有 1 * 1 的和 1 * 2 的,可以旋转。要求使用 1 * 1 的砖的数量在 [ C,D ] 内,1 * 2 的不限,问有多少种铺法。

Notes

上篇关于轮廓线DP的博文里关于轮廓线的理解有误,轮廓线不一定是工整的一整行,而只是字典序连续的 m 个格(m指地图的列数),如下图的黄色。当轮廓线转移时,黄色轮廓线的下一条轮廓线就是绿色的那条。
outline
在轮廓线前面的砖,必须是全部铺好的(或者有花不能铺);而轮廓线后面的格,都是没铺砖的;轮廓线上的格,可能有铺也可能没铺,枚举轮廓线状态就是枚举线上的格子的铺砖状态。

Analysis

考虑轮廓线 DP,从上往下、从左到右地考虑格子。
dp[p][s][u]:铺到 p 这个格,轮廓线状态为 s,用了 u 块 1 * 1 砖的方法数
对于当前格 p,有五种情况:
1. 有花,不能放砖,直接从 dp[p-1][s][u] 转移过来;
2. 没花,考虑放 1 * 1 的砖,因为轮廓线前的格子都要铺满砖(或有花不能铺,但这种情况的砖算到“已铺好”的情况),所以要求它上方的转(如果有)已经铺好,也要求 u < d;
3. 没花,考虑铺 2 * 1,要求上方有格子而且未铺砖;
4. 没花,考虑铺 1 * 2,要求左边有格子而且未铺砖;
5. 没花,考虑不铺,要求上方格子(如果有)已经铺好。
dp 数组的第一维用滚动数组的方式优化

Code

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 100, M = 10, D = 20, MOD = 1e9 + 7;

char flw[N][M+2];
int dp[2][1<<M][D+1];

void update(int p, int m, int sp, int sn, int up, int un)
{
    if(sn >> m & 1)
    {
        sn ^= 1 << m;
        dp[p][sn][un] = (dp[p][sn][un] + dp[1-p][sp][up]) % MOD;
    }
}

int main()
{
    int n, m, c, d;
    while(scanf("%d%d%d%d", &n, &m, &c, &d) != EOF)
    {
        for(int i = 0; i < n; ++i)
            scanf(" %s", flw[i]);
        memset(dp[1], 0, sizeof dp[1]);
        dp[1][(1 << m) - 1][0] = 1;
        int now = 1;
        for(int i = 0; i < n; ++i)
            for(int j = 0; j < m; ++j)
        {
            now ^= 1;
            memset(dp[now], 0, sizeof dp[now]);
            for(int s = 0; s < 1 << m; ++s) // 上一条轮廓线状态
                for(int u = 0; u <= d; ++u) // 已用 1 * 1 砖数
                    if(flw[i][j] == '0') // 有花
                        update(now, m, s, s<<1|1, u, u);
                    else // 没花
                    {
                        // 不铺
                        update(now, m, s, s<<1, u, u);
                        // 2 * 1
                        if(i && !(s >> m - 1 & 1)) // 上方有格且未铺
                            update(now, m, s, s<<1|1<<m|1, u, u);
                        // 1 * 2
                        if(j && !(s & 1)) // 左方有格且未铺
                            update(now, m, s, s<<1|3, u, u);
                        // 1 * 1
                        if(u < d)
                            update(now, m, s, s<<1|1, u, u + 1);
                    }
        }
        int ans = 0;
        for(int u = c; u <= d; ++u)
            ans = (ans + dp[now][(1<<m)-1][u]) % MOD;
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值