动态规划入门与线性 dp
引入
动态规划(Dynamic Programming,DP),是一种将原问题分为一些子问题,通过局部最优解推出全局最优解。
一般来说,做一道 dp 题有
- 设计 dp 状态:根据几个关键信息定下状态和最优化属性。
- 定下拓扑序。
- 设计状态转移方程。
- 确定初始状态、目标状态和边界条件。
基本要求
dp 的基本要求有三个:
- 最优子结构性质:即你需要保证当前状态的最优结果都是由子问题的最优结果转移而来。
- 无后效性:即当前的选择在你的状态设计下不会影响后续的决策。
- 子问题重叠:当有大量子问题时,需要把这些子问题的结果记录下来,防止多次求解导致效率低下。
dp 的分类
大致可分为如下几类:
- 序列 dp
- 背包 dp
- 区间 dp
- DAG 上 dp
- 树形 dp
- 状压 dp
- 概率 dp
- 还有些不会的,见 OI-wiki。
其中序列、背包、区间、概率 dp 都是线性 dp。
还有一些其他类型的 dp,比较有意思。
线性 dp
线性DP是动态规划问题中的一类问题,指状态之间有线性关系的动态规划问题。
虽然是线性 dp,但部分时间复杂度并不是纯线性的,因为线性 dp 的时间复杂度并不一定是线性的。
其他类型 dp:P1216 数字三角形
如果只是单纯的去考虑走最大数,你就会陷入坑中,考虑动态规划。
不要去看原图,只用看读入的数据,这样不抽象。
令
- 状态:
表示从 往下走到第 行最高得分。 - 转移:
。 - 拓扑序:由于
由 转移而来,拓扑序就是 从大到小。 - 初始状态:对于
, 。 - 目标状态:
。
时间复杂度:
序列 dp:最长上升子序列 LIS
P1020 导弹拦截 tips:这题求的两个分别是最长不上升子序列和最长上升子序列,详细证明自己搜。
B3637 最长上升子序列 tips:模板题,数据范围很小,
给定一个长度为
数据范围:
令
- 状态:
表示以第 个元素结尾的最长上升子序列长度。 - 转移:
。 - 拓扑序:由于
只由 转移而来,拓扑序就是 从小到大。 - 初始状态:
。 - 目标状态:
。
时间复杂度:
在这个数据范围中,
这也好办,只要写个离散化+树状数组/线段树优化就行了。
tips:其实这个可以贪心+二分,但不属于 dp,不谈。
时间复杂度:
序列 dp:最长公共子序列 LCS
P1439 【模板】最长公共子序列 tips:本题表面上求的是 LCS,实际为 LIS,但 50pts 暴力可以
给出一个长度为
数据范围:
- 状态:
表示 的前 个元素和 的前 个元素的最长公共子序列长度。 - 转移:
。 - 拓扑序:
从小到大, 相同则 从小到大。 - 初始状态:对于
,对于 , 。 - 目标状态:
。
时间复杂度:
关于 P1439:由于给定的是两个
时间复杂度:
背包 dp:01 背包
P1048 采药 模板题。
01 背包的模型就是给出
- 状态:
表示考虑完前 个物品(草药),耗费的背包容量(时间)为 所获得的最大价值和。 - 转移:
- 拓扑序:
从小到大。 - 初始状态:
。 - 目标状态:
。
时间复杂度:
背包 dp:多重背包
U280382 多重背包问题 别人造的例题。
多重背包的模型就是给出
- 状态:
表示考虑完前 个物品,耗费的背包容量为 所获得的最大价值和。 - 转移:分为两种解法,见下方。
- 拓扑序:
从小到大。 - 初始状态:
。 - 目标状态:
。
solution 1
转移方法:暴力。
时间复杂度:
如果
solution 2
转移方法:拆分
众所周知,如果你有了
01 背包转移见上方。
时间复杂度:
背包 dp:完全背包
P1616 疯狂的采药 模板题。
个人感觉比多重背包简单。
完全背包的模型就是给出
- 状态:
表示考虑完前 个物品,耗费的背包容量为 所获得的最大价值和。 - 转移:
。 - 拓扑序:
从小到大, 相同时 从小到大。 - 初始状态:
。 - 目标状态:
。
解释一下转移,由于每个物品可以无限选择,那么直接调用
时间复杂度:
背包 dp:分组背包
P1757 通天之分组背包 模板题。
其实和 01 背包差不多,只要把它门分个组存储,每个组选一个处理 01 背包即可。
区间 dp
P1775 石子合并(弱化版) 敲门砖,模板题。
P1880 石子合并 标准版,模板题。
区间 dp,维护的信息为一段区间的最优化属性。
在石子合并弱化版中,为了让代价尽量小,每一段区间的代价都要尽可能小。
- 状态:
表示将 合并成一堆石子的最小代价。 - 转移:
。 - 拓扑序:
从小到大。 - 初始状态:对于每个
, 。 - 目标状态:
。
时间复杂度:
石子合并的标准版中,则是将一排变成了一个环,本质没区别,只要一个环形题的常见优化套路:断环成链即可。
时间复杂度:
dp 优化
滚动数组
通过数组的滚动来实现空间优化的目的,在
当然还有一个叫做自我滚动的东西,只要注意好拓扑序是否满足只与
记忆化搜索
并不是传统意义上的优化,但它能让你的代码逻辑更加清晰。
就是在搜索时记录当前情况下的最优解,当下一次访问时直接返回结果即可。
缺点就是递归常数大一些。
本文作者:wnsyou の blog
本文链接:https://www.cnblogs.com/wnsyou-blog/p/dp.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步