铺地砖(状压DP)
Posted jpphy0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了铺地砖(状压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 1≤n×m≤300
- 结果对 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(n⋅2m⋅3m)
二进制与状态
- 一行共有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 m−j−1 位则尚未确定,但必定会由 s s s 的后 m − j − 1 m-j-1 m−j−1 位决定
- 显然,若新状态的前 j + 1 j+1 j+1位 及 原状态 s s s 的后 m − j − 1 m-j-1 m−j−1 位 一致时,将产生相同的结果,因此合并这些项将减少运算量
- 合并后,状态量是m位二进制,因此每扩展一块,将得到 2 m 2^m 2m 种新状态
- 优化后,复杂度为: O ( n ⋅ m ⋅ 2 m ) O(n\\cdot m \\cdot 2^m) O(n⋅m⋅2m)
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 n⋅m 为奇数则无解
- 滚动数组的数据清除采用使用后即清除,不采用统一的 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;
}
以上是关于铺地砖(状压DP)的主要内容,如果未能解决你的问题,请参考以下文章