Files
python/TangDou/AcWing/DP/ConvexHullTrick/【学习笔记】动态规划—各种 DP 优化.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

5.0 KiB
Raw Blame History

# 【学习笔记】动态规划—各种 DP 优化

原文链接,致敬大神

【大前言】

个人认为贪心,dp 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT)。这篇文章也是花了我差不多一个月时间才全部完成。

【进入正题】

用动态规划解决问题具有 空间耗费大、 时间效率高 的特点,但也会有时间效率不能满足要求的时候,如果算法有可以优化的余地,就可以考虑时间效率的优化。

【DP 时间复杂度的分析】

DP 高时间效率的关键在于它减少了 冗余 ,即不必要的计算或重复计算部分,算法的冗余程度是决定算法效率的关键。而动态规划就是在将问题规模不断缩小的同时,记录已经求解过的子问题的解,充分利用求解结果,避免了反复求解同一子问题的现象,从而减少 冗余

但是,一个动态规划问题很难做到完全消除 冗余

下面给出动态规划时间复杂度的决定因素:

时间复杂度 = 状态总数 × 每个状态转移的状态数 × 每次状态转移的时间

【DP 优化思路】

一:减少状态总数

(1). 改进状态表示

(2). 选择适当的规划方向

二:减少每个状态转移的状态数

(1). 四边形不等式和决策的单调性

(2). 决策量的优化

(3). 合理组织状态

(4). 细化状态转移

三:减少状态转移的时间

(1). 减少决策时间

(2). 减少计算递推式的时间

(上述内容摘自 《动态规划算法的优化技巧》毛子青 ,想要深入了解其思想的可以去看看这篇写得 超级好 的论文。)

看到这里是不是已经感觉有点蒙了呢? 本蒟蒻总结了一个简化版本:

【DP 三要点】

在推导 dp 方程时,我们时常会感到毫无头绪,而实际上 dp 方程也是有迹可循的,总的来说,需要关注两个要点:状态,决策和转移。其中 状态最为关键, 决策最为复杂。

【状态】

关于 状态 的优化可以从很多角度出发,思维难度极其高,有时候状态选择的好坏会直接导致出现 爆零 和 满分 的分化。

【决策】

与 状态 不同,决策 优化则有着大量模板化的东西,在各大书籍,文章上你都可以看到这样的话:只要是形如 XXX 的状态转移方程,都可以用 XXX 进行优化。

【转移】

转移 则指由最优决策点得到答案的转移过程,其复杂度一般较低,通常可以忽略,但有时也需要特别注意并作优化。

本文将会重点针对 决策 优化部分作一些总结,记录自己的感悟和理解。

QAQ

一:【矩阵优化 $DP$】

updata 之后由于篇幅过大,已搬出。。。。。

【学习笔记】动态规划—矩阵递推加速

TODO 挖坑待填

补充:其实质是优化 转移。

QAQ

二:【数据结构优化 $DP$】

【前言】

在一些 dp 方程的状态转移过程中,我们通常需要在某个范围内进行择优,选出最佳决策点,这往往可以作为 dp 优化的突破口。

数据结构的使用较灵活,没有一个特定的模板,需要根据具体情况而定,选择合适的方案。由于状态转移总是伴随着 区间查询最值,区间求和 等操作,即 动态区间操作,所以 平衡树 可以作为一个有用的工具,但考虑到代码复杂度,使用 树状数组 或者 线段树 将会是一个不错的选择。

其实质是优化 决策。

1.【维护合适的信息】

以 The Battle of Chibi [$UVA12983$] 为例,大概题意就是计算在给定的序列中严格单调递增子序列的数量,并对 1e9+7 取模,给定序列长度小于等于 1000 。

方程应该是比较好推的,用 dp[i][j] 表示由序列中在 j 之前的数构成并以 a[j] 结尾的子序列中,长度为 i 的子序列的数量。则: \large dp[i][j]=\sum dp[i−1][k] ,其中 k<j 且 a[k]<a[j] 。

$Q:$状态用一维表示行不行? A:如果是一维表示,那么就是表示由序列中在 j 之前的数构成并以 a[j] 结尾的子序列的数量。这时考虑一下状态转移,看看它的前序状态是怎么样转移过来的:枚举它前面所有的数字,a[k]<a[j],这样不行吗? 不行,比如1~ 2~ 2~ 2~ 6 ~ 3~ 3~ 4,因为存在重复数字2,会造成统计重复,无法去重。 办法就是增加一个维度,还要记录以$k$结尾时的子序列长度,就解决了去重的问题。

对于决策点 dp[i−1][k] 这里出现了 3 个信息:

  1. 在原序列中的位置 k<j 。
  2. a[k]<a[j] 。
  3. dp[i−1][k] 的和。