本文介绍一些使用动态规划算法
来求解题集,以进一步加深对动态规划算法的理解。
1. 瓷砖铺贴问题
题目描述
有一块大小是2*n
的墙面,现在需要用两种规格的瓷砖铺满,瓷砖规格分别为2*1
和2*2
,请计算一共有多少种铺设方法。
输入
输入的第一行包含一个正整数T(T<=20),表示一共有T组数据,接着是T行数据,每一行包含一个正整数N(N<=30),表示墙面的大小是2行N列。
输出
输出一共有多少种铺设的方法,每组数据的输出占一行
样例输入
3
2
8
12
样例输出
3
171
2731
1.1 动态规划求解
本题用动态规划来解决比较简单。我们先找出动态规划的递推式:在进行瓷砖铺贴时有两种砖可选择,设2*1
的砖为A,2*2
的砖为B。
1)当我们在最开始铺上A砖时,有两种选择
-
竖着铺A砖,此时后面砖的铺法就是dp[i-1]
-
横着铺A砖,此时后面砖的铺法就是dp[i-2]
2) 当我们在最开始铺B砖时,只有一种选择
- B砖占据前面四个格子,则后面砖的铺法就是dp[i-2]
综上得出递推公式:
dp[1] = 1
dp[2] = 3
dp[i] = dp[i - 1] + 2 * dp[i - 2]
由上述递推式写出代码如下:
2. 铺瓷砖问题(状态压缩动态规划)
本题转载自铺瓷砖问题(状态压缩动态规划)
2.1 问题简单描述
在一个N行M列
的格子里,现有1*2
大小的瓷砖,可以横着或者竖着铺。问一共有多少种方案,可以将整个N*M
的空间都填满。
示例:
2.2 问题分析
1) 因为每块砖的面积是2,所以总面积M*N
必须是偶数才能铺满。如果是基数,则方案数显然为0.
2)分析一下覆盖的状态,用二进制来代表具体覆盖的方案:
用二进制来代表每一行的覆盖状态:(0,1)
代表竖着铺,(1,1)
代表横着铺。
铺满的时候最后一排必然全部都是1。
状态转移
此问题的状态转移比较复杂: 上一行的某个状态对应当前行的多个状态;当前行的某个状态也可以来自上一行的多个状态。
状态转移示意图如下:
通过观察我们可以看到上一行到下一行状态转移的关系如下:
(注: 此处上一格
代表上一行同一列位置的格子,后一格
代表同一行右侧的格子)
对于当前行
的某一格来说:
1) 如果上一格是0,当前格必须是1
2) 如果上一格是1
2.1) 当前格可以是0,也可以是1,说明既可以竖着铺,也可以横着铺
2.2) 如果当前格是横铺的第一个1,则后一格必须也是1,并且后一格的上一格不能为0
据此我们可以设计判断当前行能否从上一行状态转移过来的逻辑。
参看如下例子:
dp[i][10011] += dp[i-1][01100]
dp[i][10011] += dp[i-1][01111]
初始状态
第一行是没有上一行的,为了避免单独写第一行的逻辑。我们可以假设在第一行之上还存在初始行,我们把初始行的状态设为全1的时候方案为1,其他状态方案为0。 这样同样的逻辑我们可以转移到合法的第一行状态。
(注意:初始行只是提供初始状态,不需要考虑初始行本身全1是否合法)
2.3 代码实现
具体代码如下:
算法的时间复杂度为N*(4^M)
, 因为M对时间的影响较大,如果M>N,可以交换二者,确保M的值较小。这样可以提高速度。
2.4 空间压缩
因为只需要用到当前行和上一行的状态,所以只需要两个2^M的数组来保存状态即可
2.5 总结
这是一道经典的状态压缩动态规划问题。本文用整行作为状态来设计动态规划的算法,思路清晰,代码简洁。
本方法时间复杂度较高,还可以通过轮廓线动态规划的方法来进一步优化时间复杂度。
读者可以参考后续的文章 铺瓷砖问题(二)。
[参看]:
-
贴瓷砖问题——动态规划
-
刷过的算法题
-
铺瓷砖问题