dp 基础

本文默认读者接触过基本的 dp 思想。

大约是对 dp 的一个重新解构,因为近期发现自己对“概念”的理解真的很不深入。

同样的,本博客的 dp 部分也会在近期重构。


动态规划可以看作,在所有状态构成的状态空间中,建立了一张 DAG(有向无环图)。

DAG 保证无环,也就是保证了 dp 的无后效性决策/转移则是这张 DAG 上的边。

状态本身,是 DAG 上的节点,它们代表的不仅仅是某一个情况,而是以它结尾的多条转移链对应的最优解,这多条转移链构成的子图被称作一个问题

阶段是由同层(你可以理解为 bfs 时深度相同)的节点构成的,它的意义是对原问题的子问题进行划分,方便求解。

最优子结构是应用动态规划的大前提,它的内容是,一个问题的最优解,应当由它的子问题的最优解导出,换句话说,它给转移和状态加上了束缚,使得它们能够正确转移。

我时常分不清楚贪心和 dp 的区别,因为它们能解决的问题同样都应该拥有最优子结构。

不过现在看来,正是这个共同点成为了找到它们不同点的关键,注意到上面粗体高亮的“多条”了吗?

区别就在于此,贪心的子问题永远是链,dp 的子问题是子图,复杂程度不严格大于贪心,可以看两张图来得到更形象的理解:

这是贪心的转移图:

greedy-trans

这是 dp 的转移图:

dp-trans

为了有更形象的理解,举几个简单的例子:

  • 数字三角形:
    • \(dp(i, j)\) 表示从 \((1, 1)\) 走到 \((i, j)\),可以获得的最大价值。
    • 这个的阶段应当是行,这是显然的,如果选择列的话,就没有办法覆盖整个状态空间了。
    • 转移是考虑从 \(dp(i - 1, j)\)\(dp(i - 1, j - 1)\) 转移过来,加上 \((i, j)\) 的权值就行了。
    • 这告诉我们,在满足最优子结构性质的前提下,我们转移只需要关心当前阶段的情况就行了,之前阶段,或者进一步说,之前的子问题的最优解,都已经被包含在了 dp 状态本身里了。
  • Longest Increasing Subsequence:
    • \(dp(i)\) 表示考虑前 \(i\) 个位置,以 \(i\) 结尾的 IS 的最大长度。
    • 转移是考虑从所有的 \(j\text{ s.t. }a_j < a_i\)\(dp(j)\) 转移过来,这里就是一个“约束”,用来确定哪些状态是合法的,可以正确转移过来的。
    • 这是一个线性序列上的 dp,阶段直接就是下标,或者说我们的子问题是考虑了一段前缀的。
    • 阶段通常需要具有一些特殊的性质来支撑它作为阶段。
    • 比如说,一个位置的答案依赖它之前的位置的答案,那么我们就可以以位置为阶段,每次考虑一段前缀,这就是线性 dp 了。
    • 或者说,一个节点的答案依赖于子树的信息,那么我们就以子树为阶段,每次考虑一个子树,这就是树形 dp 了。
    • 又有些时候,出于方便考虑,我们直接对答案的数位进行考虑,不关心做贡献的树具体是多少,只关心这一位的贡献,这就是数位 dp 了。
  • Longest Common Subsequence
    • \(dp(i, j)\) 表示 \(a[1..i],b[1..j]\) 的 CS 的最大长度。
    • 转移需要考虑多种情况,一种是 \(a[i] = b[j]\),另外一种是不等。
    • 换句话说,一个阶段中的状态可能有不同的分类,为了覆盖完整个状态空间,我们需要转移全。
    • 这里是最优化问题,所以直接转移就行了,只需要考虑不漏。
    • 如果是计数问题,那么就需要考虑是否会重复,这就要求一种合法的基本情况和一个合法的转移序列是唯一对应的,这样,我们处理的信息天然就满足不重不漏性质,只需要保证转移合法,就能覆盖整个状态空间。
  • Random Walk:
    • 一个 bot,每次可以往八连通的格子里走,初始在一个位置,问走出网格的期望。
    • 这种期望相关的 dp 的一大特征就是,转移很容易成环,因为 bot 可以不停转圈,但是这样的状态仍然合法。
    • 我们先不管这么多,对每个位置把转移方程列出来,显然会成环对吧。
    • 这个时候,把所有的转移方程当成一个线性方程组,用高斯消元就可以做了。

我之前考虑 dp 的时常会犯一个错误,一直不知道该怎么描述,现在发现其实是对最优子结构理解不清晰导致的:

比如说,没想清楚这个问题是否具有最优子结构,或者是对着一个没有最优子结构的信息做 dp。

举个例子,哎呦似乎有点难举出来,之后如果再遇到就写(希望不要遇到了)

但是教训是,可以尝试假设它有最优子结构,然后反证(证明比举反例好,反例容易想错)。

精简一点,dp 只需要考虑这几个问题:

  1. 阶段是?状态是?
  2. 咋转移?不重不漏了吗?
  3. 最后的就是考虑优化了,这部分可以参见本目录下的“dp 优化”。

最后更新: July 22, 2023