动态规划基础
理解动态规划的思考方式
从集合的角度理解DP问题
状态表示 f(i,j)(存的是所有选法的集合的最大值),考虑清楚需要几维来表示我们的问题
集合:每一个状态都是表明一个集合,在背包问题里表示所有选法的集合
条件:
- 只考虑前i个物品
- 总体积不超过 j
属性:集合的最大值,最小值,元素数量
状态计算 ,如何将每个状态计算出来
目标:求f(N,V)
状态计算一般表示集合的划分:把当前集合划分为若干个更小的子集,使得每一个子集我们都可以由前面更小的状态计算得到
例如f(i,j)可以包含两类:
- 左边类是不包含第i个物品的选法
- 右边类是包含第i个物品的选法
实际的最大值是两类取一个max
DP问题的优化一般只是等价变形,所以写出朴素解法十分重要
DP问题一定要结合题目来理解,上面的思考方式就像是骨架,根据具体问题填补具体的血肉
背包模型
01背包问题
什么是01背包?
每件物品最多只能用一次
在背包容量的范围内如何挑选物品,让总价值最大
朴素做法
1 | //枚举所有状态f[0,0]~f[n,m] |
一维做法
第i层的状态只依赖于第i-1层;不超过的总体积j只依赖于<=j的状态,因此可以优化到一维来做
1 | int f[N];//直接去掉一维 |
完全背包问题
每件物品可以用无限次
解题思路
分成若干组,分成k类:
不妨设第i个物品选了k个
曲线救国:
- 去掉k个物品i
- 求Max,f[i-1,j-k*v[i]]
- 再加回来k个物品i
综上,f[i,j] = f[i-1,j-k*v[i]] + k * w[i]
朴素做法
1 | //最坏情况下:O(n*m^2)) |
二维做法
1 | //f[i,j] = Max(f[i-1][j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,f[i-1,j-3v]+3w,...) |
一维做法
完全背包问题的终极解法
1 | int f[N]; |
多重背包问题
每件物品最多有Si个
解题思路
枚举第i个物品选多少个,根据第i个物品选多少个来将我们所有的选法分成若干种类别:
其实就是朴素版本的完全背包问题:f[i,j] = f[i-1,j-k*v[i]] + k * w[i]
朴素版本
1 | //最坏情况下:O(n*m^2)) |
优化版本:二进制优化
c++1s最多算1亿次,超过一亿次会超时
从0~s中的任何一个数都可以被拼凑出来(由1,2,4,2^k, … , c)。
将s个物品i拆分程log(s)个新的物品,新的物品只能用一次
对所有这些新出来的物品做一遍01背包即可,时间复杂度为O(N*v*log(s))
分组背包问题
状态表示的集合:只能从前i组物品中选,且总体积不大于j的所有选法
枚举第i组物品选不选,选哪个
朴素解法
1 | for(int i=1;i<=n;i++) |
优化解法
1 | int f[N];//直接去掉一维 |
线性DP
数字三角形
状态表示
- 集合:从顶点到(i,j)的所有路径
- 属性:MAX
状态计算
复杂度:状态数量*转移数量
1 | const int N = 510,inf = -1e9; |
最长上升子序列
可以按照顺序跳着选择
状态表示
- 集合:所有以第i个数为结尾的数值上升的子序列
- 属性:子序列的最大长度
状态计算
朴素版
1 | const int M = 1010; |
优化版
最长公共子序列
状态表示
- 集合:所有由第一个序列的前i个字母,和第二个序列的前j个字母的构成的公共子序列
- 属性:公共序列的最大值
状态计算
求max是可以重复的,只要不漏掉某一元素即可
1 | const int M = 1010; |
编辑距离
区间DP
不同的合并顺序需要的体力是不同的
状态表示
- 集合:从第i堆石子到第j堆石子合并成一堆石子的合并方式
- 属性:Min
状态计算
按区间长度从小到大枚举
计数类DP
整数划分问题
数位统计DP
状态压缩DP
使用整数来表示状态,把这个整数看作是二进制数,每一位是0是1代表不同的情况,因此位数(n)最多取到20位(1e6种状态)
树形DP
接受了之后思维跨度就不高
记忆化搜索
每一道动规题都可以用递归的方法做
- Post title:coding笔记: 动态规划
- Post author:sixwalter
- Create time:2023-08-05 11:14:26
- Post link:https://coelien.github.io/2023/08/05/coding-solution/DP/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.