铺地砖(状压DP)

问题

  • 求用 1 × 2 1\times 2 1×2 的地砖铺设 n × m n\times m n×m 的地面的方案数,地砖不能相互覆盖.
    • 1 ≤ n × m ≤ 300 1\leq n\times m \leq 300 1n×m300
    • 结果对 1 e 9 + 7 1e9+7 1e9+7 取模

分析

扩展过程

在这里插入图片描述

  • 当某一行扩展完毕时,这一行可能铺满,也可能留有若干空缺,因此扩展结果可能有 2 m 2^m 2m 情况
  • 扩展新的一行时,对于上一行的每种扩展结果都需要进行m次扩展,有可能得到 2 m 2^m 2m 结果
  • 扩展过程的复杂度: O ( n ⋅ 2 m ⋅ 3 m ) O(n\cdot 2^m \cdot 3^m) O(n2m3m)

二进制与状态

  • 一行共有m块,已铺地砖的块视为 0 0 0,未铺地砖的空缺块位视为 1 1 1,则可用m位二进制表示

扩展优化

  • 使用BFS方式处理扩展过程
  • 优化扩展结果表示
    • 设扩展到了 [ i ] [ j ] [ s ] [i][j][s] [i][j][s],即第 i i i 行第 j j j 例,且上一行的状态是 s s s
    • 新状态的前 j + 1 j+1 j+1位已经确定,而后 m − j − 1 m-j-1 mj1 位则尚未确定,但必定会由 s s s 的后 m − j − 1 m-j-1 mj1 位决定
    • 显然,若新状态的前 j + 1 j+1 j+1位 及 原状态 s s s 的后 m − j − 1 m-j-1 mj1 位 一致时,将产生相同的结果,因此合并这些项将减少运算量
    • 合并后,状态量是m位二进制,因此每扩展一块,将得到 2 m 2^m 2m 种新状态
  • 优化后,复杂度为: O ( n ⋅ m ⋅ 2 m ) O(n\cdot m \cdot 2^m) O(nm2m)

dp数组优化

  • 维护上次扩展结果
  • 维护本次扩展结果
  • 显然,滚动数组即可, p r e [ s ] pre[s] pre[s] c u r [ s ] cur[s] cur[s], 表示扩展结果是 s s s 的方案的可能数量

其它优化

  • 地面 n ⋅ m n\cdot m nm 为奇数则无解
  • 滚动数组的数据清除采用使用后即清除,不采用统一的 m e m s e t memset memset 方式

代码

/* 铺地砖 状压dp */
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int md = 1e9+7;
const int MXS = (1<<17)+5;
ll dp[MXS<<1], *pre, *cur;
int main(){
	int t, S, N, M;
	scanf("%d", &t);	
	while(t--){
		scanf("%d%d", &N, &M);
		if(M > 17) M ^= N, N ^= M, M ^= N; // 交换
        if(N&1 && M&1) { printf("0\n"); continue; }
		S = (1<<M)-1;
        pre = dp, cur = dp+MXS;
        memset(dp, 0, sizeof dp), pre[0] = 1; // 设定初态
		for(int n = 0; n < N; ++n){
			for(int m = 0; m < M; ++m){
				for(int s = 0; s <= S; pre[s++] = 0){
					if(pre[s] == 0) continue; // 未出现的状态
					if(s&(1<<m)){ // 向上铺
						cur[s&~(1<<m)] = (cur[s&~(1<<m)] + pre[s])%md;
						continue;
					}					
					cur[s|(1<<m)] = (cur[s|(1<<m)] + pre[s])%md; // 不铺
					if(s&(1<<m-1) && m > 0)// 向左铺
						cur[s&~(3<<m-1)] = (cur[s&~(3<<m-1)] + pre[s])%md;
				}
                swap(pre, cur);
			}
		}
        printf("%d\n", pre[0]);
	}
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jpphy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值