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
指地图的列数),如下图的黄色。当轮廓线转移时,黄色轮廓线的下一条轮廓线就是绿色的那条。
在轮廓线前面的砖,必须是全部铺好的(或者有花不能铺);而轮廓线后面的格,都是没铺砖的;轮廓线上的格,可能有铺也可能没铺,枚举轮廓线状态就是枚举线上的格子的铺砖状态。
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;
}