今天也是为了cc,努力奋斗的一天ヾ(≧▽≦*)o
前面几节介绍了动态规划的相关概念,并求解了一些经典的动态规划模型。但是在实际碰到新的问题时,初学者总是容易陷入头脑一片空白、完全无法设计状态的情况,这是正常现象,因为动态规划本身就需要经验的积累和大量做题才能有较大的提升。不过从上面的经典模型中还是能总结出一些规律性的东西,由于动态规划的重点在于状态上,所以这些规律也是用于状态的定义上的。
动态规划模型回顾
先把前面介绍过的动态规划模型列举如下:
(1)最大连续子序列和
dp[i]
:以A[i]
作为结尾的连续序列的最大和。
(2)最长不下降子序列(LIS)
dp[i]
:以A[i]
作为结尾的最长不下降序列长度。
(3)最长公共子序列(LCS)
dp[i][j]
:字符串A的i
号位和字符串B的j
号位之前的LCS长度
(4)最长回文子串
dp[i][j]
:S[i]
至S[j]
所表示的子串是否为回文子串
(5)数塔DP
dp[i][j]
:从第i
行第j
个数字出发的到达最底层的所有路径上所能得到的最大和。
(6)DAG最长路
dp[i]
:从i
号顶点出发能获得的最长路径长度
(7)01背包
dp[i][v]
:前i
件物品装入容量为v
的背包中能获得的最大价值。
(8)完全背包
dp[i][v]
:前i
件物品装入容量为v
的背包中能获得的最大价值。
规律一
先看(1)~(4),这4个都是关于序列或字符串的问题(特别说明:一般来说,“子序列”可以不连续,“子串”必须连续)。可以注意到:
- (1)(2)设计状态的方法都是“令
dp[i]
表示以A[i]
为结尾的XXX”,其中XXX即为原问题的描述,然后分析A[i]
的情况来进行状态转移; - (3)(4)由于原问题本身就有二维性质,因此使用了“令
dp[i][j]
表示i
号位和j
号位之间XXX”的状态设计方式,其中XXX为原问题的描述(最长回文子串中的状态和原问题有关;当然,最长回文子串的状态也可以设计成“令dp[i][j]
表示S[i]
至S[j]
的区间的最长回文子串长度”,并且可解)。
这就给我们一些启发:
当题目与序列或字符串(即为A)有关时,可以考虑把状态设计成下面两种形式,然后根据端点特点去考虑状态转移方程。
(1)令dp[i]表示以A[i]结尾(或开头)的XXX。
(2)令dp[i][j]表示A[i]至A[j]区间的XXX。
其中XXX均为原问题的描述。
规律二
接着来看(5)~(8),可以发现它们的状态设计都包含了某种“方向”的意思。如:
- 数塔DP中设计为从点(i,j)出发到达最底层的最大和;
- DAG最长路中设计为从i号顶点出发的最长路;
- 背包问题中则设计成dp[i][v]表示前i件物品放入容量为v的背包中能获得的最大值。
这又说明了一类动态规划问题的状态设计方法:
分析题目中的状态需要几维来表示,然后对其中的每一维采取下面的某一个表述:
- 恰好为i。
- 前i。(背包问题中用到)
在每一维的含义设置完毕之后,dp数组的含义就可以设置成“令dp数组表示恰好为i(或前i)、恰好为j(或前j)…的XXX”,其中XXX为原问题的描述。接下来就可以通过端点的特点去考虑状态转移方程。
常见的维度
我们说动态规划要设计状态数组,那么状态数组的维度通常有哪些东西呢?如下:
- 一个已有数组变量的数组下标范围(如LIS,LCS等等);
- 一个已有变量的取值范围(如背包问题中的v)
动态规划与DAG的关系
最后需要说明的是,在大多数的情况下,都可以把动态规划可解的问题看作一个有向无环图(DAG),图中的结点就是状态,边就是状态转移方向,求解问题的顺序就是按照DAG的拓扑序列进行求解的。 从这个角度可以辅助理解动态规划,建议读者能结合讲解过的几个动态规划模型予以理解。
其实,我们知道动态规划可以使用递归来实现,VisuAlgo网站提供了常见动态规划问题的可视化递归实现,在可视化中,我们可以清楚看到动态规划可解的问题可看作一个有向无环图(DAG),即递归树的形式。