题目
有一个
N
×
M
(
N
≤
5
,
M
≤
1000
)
N\times M(N\le5,M\le1000)
N×M(N≤5,M≤1000)的棋盘,现在有
1
×
2
1\times2
1×2及
2
×
1
2\times1
2×1 的小木块无数个,要覆盖整个棋盘,有多少种方式?
答案只需要
m
o
d
1
0
9
+
7
\bmod 10^9+7
mod109+7 即可。
状压DP简述
状压DP,就是把一个集合中每个元素的状态(一般为
0
/
1
0/1
0/1 )用一个二进制数表示。
对于第
i
i
i位,
0
0
0代表其中没有
i
i
i;
1
1
1代表其中有
i
i
i
也可以认为状压DP就是把一个集合压缩为了一个方便操作的整数。
基本集合操作
有两个集合
A
,
B
A,B
A,B,其二进制数位
a
,
b
a,b
a,b。
1.并集
A
⋃
B
A\bigcup B
A⋃B对应
a
a
a
o
r
or
or
b
b
b
2.交集
A
⋂
B
A\bigcap B
A⋂B对应
a
a
a
a
n
d
and
and
b
b
b
3.补集对应
n
o
t
not
not
a
a
a
题目解法
当发现了
N
≤
5
N\le 5
N≤5时,就意味着其中一位的状态可以进行压缩。
状态就可以这样设
d
p
i
,
S
dp_{i,S}
dpi,S:前
i
i
i列全部填满,第
i
+
1
i+1
i+1列在之前的填充中填充状态为
S
S
S的情况数。
这样就利用了
N
N
N的特性。
对于转移,先枚举
d
p
i
,
S
dp_{i,S}
dpi,S搜索所有状态,把第
i
i
i列填满,到达状态
d
p
i
+
1
,
S
′
dp_{i+1,S'}
dpi+1,S′。
搜索中只要有了空位,就尝试填入
1
×
2
1\times 2
1×2的小木块,
如果有连续两个空位,还能填入
2
×
1
2\times 1
2×1的小木块。
代码
void dfs(int i,int j,int s,int s1,int s2){
if(j == n){
if(s1 == (1 << n) - 1) dp[i + 1][s2] = (1LL * dp[i + 1][s2] + dp[i][s]) % mod;
return;
}
if((s1 & (1 << j)) != 0) dfs(i,j + 1,s,s1,s2);
else{
dfs(i,j + 1,s,s1 + (1 << j),s2 + (1 << j));
if(j < n - 1 && ((s1 & (1 << (j + 1))) == 0)) dfs(i,j + 2,s,s1 + (1 << j) + (1 << (j + 1)),s2);
}
}
我的思考
这题是一道难题,需要dp与搜索的完美结合。
我在调试花了许多时间。其中,这题的核心思想是对每一列进行状态压缩、用搜索的方式进行填充和转移。
这里的搜索不仅仅是对下一个状态进行枚举,也是填充这一行的过程。
写搜索时,要明确题目搜索的目的,也可以进行画图思考,这样能帮助我们大大缩短因为不明确思路而造成的大量的调试。
dp的调试重点在明确状态背后的含义(具体的定义),还有一些边界的取值范围。
注意了以上两点,相信能减少调试的次数和时间。