0%

DP(一)

线性 DP

笔者认为,DP 也是一种思想,是一种以空间换时间的思想。

DP 一般存在三种实现方式:

  • 记忆化搜索
  • 拓扑排序
  • 扫表

三种实现方式各有一定的优劣,将在后文中分析。

DP 从形式上分为三部分:

  • 状态
  • 状态转移
  • 初始化

其中实现状态转移的式子常被称为状态转移方程。

而转移的方式,也在部分场景下被细分为“顺推”和“逆推”。

“顺推”为用当前状态去更新可以转移到的状态,为“到哪去”。

“逆推”为用可以转移到的状态来更新当前状态,为“从哪来”。

“顺推”和“逆推”在部分情况下有优劣,但一般无影响,将在后文考虑。

DP 转移时需要保证用来转移的状态是已经正确的。

DP 的时间复杂度为 $O(状态数\times 转移时间)$。

本文将介绍若干 DP 入门例题帮助 DP 思维的建立。

线性 DP 是指在线性结构上进行转移。在实际问题中,一般在序列上和矩阵上考虑较多。

矩阵 DP:

$[IOI1994]$ 第一次在算法竞赛引入了动态规划的思想。

例 $1$:

给定一个 $n$ 层的数字三角形每次可以从 $(x,y)$ 走到 $(x+1,y),(x+1,y+1)$,求从 $(1,1)$ 出发经过路径上数字之和的最大值。

状态:

设 dp$[x][y]$ 为从起点到 $(x,y)$ 的答案。

状态转移:

$(x,y)\rightarrow(x+1,y),(x+1,y+1)$

$(x,y)\leftarrow(x-1,y),(x-1,y-1)$

所以写出 DP 式子:

  • 顺推:dp[x+1][y]=max(dp[x][y]+a[x+1][y],dp[x+1][y]),dp[x+1][y+1]=max(dp[x][y]+a[x+1][y+1],dp[x+1][y+1])
  • 逆推:dp[x][y]=max(dp[x-1][y],dp[x-1][y-1])+a[x][y]

在一般的 DP 问题中,普遍采用扫表的方式实现 DP。扫表即使用循环枚举状态,所以对于扫表法来说,枚举的顺序很重要。

此题中因为每一层的状态一定是在相邻的两层之间转移,所以按层枚举,而层内的顺序则不影响。

初始化:

从 $(1,1)$ 开始,所以可以将 $(1,1)$ 设为初始状态,初始化 dp$[1][1]=a[1][1]$,后从第二层开始枚举。同时因为求的是极大值,所以要将 dp 数组初始化为极小值。

最后的解即为 dp$[n][n]$。

时间复杂度:$O(n^2)$。

空间复杂度:$O(n^2)$。

DP 空间换时间,所以空间复杂度普遍偏高,内存超限也多出现在 DP 问题中。

序列 DP:

例 2:

给定一个长度为 $n$ 的序列 $a_i$,求最长上升子序列。

状态:

设 dp$[i]$ 为以 $a_i$ 结尾的最长上升子序列长度。

状态转移:

$(i)\rightarrow (j)(a_j>a_i)$

$(i)\leftarrow(j)(a_j<a_i)$

这里一个小区别就是转移的状态不确定了,不是固定的。

所以需要去枚举转移:

  • 顺推:For(j,i+1,n) if(a[j]>a[i]) dp[j]=max(dp[j],dp[i]+1)
  • 逆推:For(j,1,i-1) if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1)

初始化:

每一个位置本身可以是最长上升子序列的开始,所以可以将 $(1),…,(n)$ 都初始化为 $1$ 以作为初始状态。

在序列上,每一个位置的答案一定是由它之前的转移过来,所以按顺序枚举即可。

最后的解即为:dp$[n]$。

时间复杂度:$O(n^2)$。

空间复杂度:$O(n)$。

事实上,此处的 DP 转移可以优化做到 $O(n\log n)$ 的时间复杂度,将在之后的文章提到。

总结以上两个例子,矩阵中进行 DP,往往设 dp$[x][y]$ 表示从起点到 (x,y) 的答案;序列中进行 DP,往往设 dp$[x]$ 表示从 $1$ 到 $x$ 的答案。在具体的题目中,也会有相应的扩展。

扩展:

DP 和 贪心:

DP 和贪心都是一种思想,它们之间没有明确的界限。可以说贪心是一种特殊的 DP,也可以认为 DP 是一种特殊的贪心,自行理解即可。

考虑下面一个问题:

例 3:

求用面值 $1,3,5$ 分的硬币支付 $x$ 分钱的最少硬币数量。

考虑贪心:优先使用 $5$ 分,直到不能用 $5$ 分后再用 $3$ 分,最后用 $1$ 分。

证明:

引理:最后的硬币方案,一定最多 $1$ 个 $3$ 或者 $2$ 个 $1$。

反证。若存在 $2$ 个 $3$,则可以替换成 $1$ 个 $1$ 和 $1$ 个 $5$ 不会更劣。

若有 $3$ 个 $1$,则可能替换成 $1$ 个 $3$,更优。

也就是说用 $1$ 和 $3$ 只能支付小于 $5$ 的面额。而优先使用 $5$ 之后,剩下的只能是 $0,1,2,3,4$,分别考虑也可证明策略的正确性。

例 4:

求用面值 $2,4,5$ 分的硬币支付 $x$ 分钱的最少硬币数量,或判断无解。

此时,贪心显然错误。考虑 $x=6$ 时,贪心策略无解。

考虑 DP。

状态:

设 dp$[x]$ 表示支付 $x$ 分钱的最少硬币数量。

状态转移:

  • $(x)\rightarrow (x+2),(x+4),(x+5)$
  • $(x)\leftarrow(x-2),(x-4),(x-5)$

所以写出 DP 式子:

  • 顺推:dp[x+2]=max(dp[x+2],dp[x]+1),dp[x+4]=max(dp[x+4],dp[x]+1),dp[x+5]=max(dp[x+5],dp[x]+1)
  • 逆推:dp[x]=max(dp[x-2],dp[x-4],dp[x-5])+1

初始化:

初始情况下没有任何硬币,所以应该选择 $(0)$ 作为初始状态并初始化 dp$[0]=0$。同时要注意无解的情况。所以要特殊处理无解,一般可以之间置为极大值(求解最小值)或者极小值(求解最大值)。

容易发现,大金额的状态是由小金额的状态转移的,所以从小到大枚举金额即可。

最后的解即为:dp$[x]$。

Tips:在后续的内容,为了避免不必要的内容,除非必要,将统一使用逆推。