动态规划
从集合的角度理解
1. 状态表示 f(i, j)
- 集合:只考虑前i个物品并且总体积<=j的所有选法
- 属性:Max + Min + 数量
2. 状态计算——集合划分(不含i, 含i)
画出面包图,表示出可能的各种状态
分析方法:先不考虑第 i 件物品,变为 f(i-1, j)和f(i -1, j - vi) + wi
背包问题
01背包
每个物品用一次
滚动数组优化:(原因)
- f(i)只用到了f(i - 1),所以可以把前面的都省掉,只看j
- 体积 j - v[i] <=j——>01背包倒序求,防止重复取用
完全背包
每个物品无限个
属性:(Max)
1. 去掉k个i
2. 求Max,f[i - 1, j - k * v[i]]
3. 加上k个i
f[i, j] = max( f[i - 1, j - v[i] * k] + w[i] * k) ;
优化:进行一个对齐的操作
顺序:小——>大
多重背包
每个物品有限定个
f[i, j] = max( f[i - 1, j - v[i] * k] + w[i] * k) ;
二进制分组优化
打包
1. 1,2,4,8,…,2^k^,c (c < 2^{k+1}^)
(滑动窗口最大值:单调队列优化)
分组背包
i个——>i组
线性DP
维度升序考虑
最长上升子序列
集合:所有以第 i 个数结尾的上升子序列
属性:Max
$$
a_j = Max(f[j] + 1)\\
满足a_j < a_i,j = 0,1,2,…,i-1
$$
保存序列:记录转移过程
优化:存结尾最小的上升子序列
因为小的末尾一定可以接到大的头上
二分找(其实STL也很好)
最长公共子序列
集合:所有再第一个序列的前 i 个字母中出现,且再第二个序列的前 j 个
分三种状况分析,一个不等2,两个都等1
dp[i][j] = max (dp[i-1][j], dp[i][j-1]);
if(a[i] == b[j])
dp[i][j] = dp[i-1][j-1] + 1;
最短编辑距离
集合:所有将a变成b的操作方式
状态计算:
- 删:f(i-1, j) + 1
- 增:f(i, j-1) + 1
- 改:f(i-1, j-1) + (a[i] != b[j]) (不等就要加一次
f(i, j) = min(删,增,改)
区间DP
集合:所有将第 i 堆石子合并为一堆石子的合并方式
属性: Min
过程分析:
(最后一步)最后的合并代价 + 前缀和求所有质量之和
$$
f[i][j] = min( s[j] - s[i -1] + f[i, k] + f[k +1, j])
$$
枚举长度、起点、分界点k
计数DP
整数划分
- 完全背包做法
关键在于抽象转化
集合:1~i 选,体积恰好是 j 的数量 - 其他
集合:总和为 i ,总个数为 j
数位统计DP
count(n, x) : 1 ~ n 中 x 出现的次数
来自wx大佬:
状态压缩DP
蒙德里安的梦想
横着放完,竖的就自然出来了
f[i][j] : 前 i -1 列已经摆好,从i - 1列伸出到第 i 列的所有方案的状态( j 记录哪些列伸出了横向方格)(二进制表示)
i 表列,j、 k 表行
f[i, j] += f[i - 1, k]
满足条件:
1. (j & k) == 0 (没冲突)
2. j | k 不存在连续奇数个0 (没空隙)
纵向看,必须要有连续偶数个空位(用来放置竖的方块)
最短Hamilton路径
- f[i][j]:所有从0走到 j ,走过的所有点是 i 的所有路径
- 0—…—>k—> j
f[i - (1 << j)][k] + w[k][j]
i >> j & 1 代表 j 在 i 状态里
i - (1 << j) 代表 除去 j 这个状态
树形DP
经典老题:没有上司的舞会
(某人因为root搞错)
建议看我的视频(最好别hhh
https://www.bilibili.com/video/BV1wL411w7t1?spm_id_from=333.999.0.0
记忆化搜索
经典老题:滑雪