一.区间DP
dp[l][r]:l,r为左右端点
1.基于左右两端点的可能性讨论
时间复杂度一般为O(n^2)
2.基于区间划分点的可能性讨论
时间复杂度一般为O(n^3)
3.前两种混合
二.背包DP
dp[i][j]:i为物品种类,j为背包属性(重量等)
1.背包问题一般先枚举物品种类,再枚举其他变量。
因为先枚举物品种类,会使递推式中物品种类选择是固定的。(比如先枚举的1物品,在2物品枚举之前,不会出现2物品,只有1物品)
而先枚举比如背包重量,其中的物品种类是不确定的。(比如枚举到重量为k,其中的物品种类你无法确定)
因此先枚举物品种类可以确保不出现重复问题。
2.分类:
设当前物品重量为k
01背包:
if j >= weight[i]:
dp[i][j] = max(不选当前物品: dp[i-1][j],
选当前物品: dp[i-1][j-weight[i]] + value[i])
else:
dp[i][j] = dp[i-1][j] # 无法选当前物品
01背包滚动数组优化:for i in range(0,n-1): # 遍历物品
for j in range(W,weight[i]) : # 逆序遍历容量
dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
完全背包:dp[i][j] = max(dp[i][j],dp[i][j-k])(枚举j)
多重背包,将每个物品数量转化为二进制,dp式与01背包一样
分组背包:01背包套01背包
子集和、等和分割、目标组合数等问题均可转化为背包模型
三.最简易的DP优化——滚动数组优化
正序与逆序
注意,正序或逆序取决于DP式传递的方向,下面使用dp[i] = dp[i-k]*(……)为例
如果正序,则会出现覆盖的情况
dp[i] = dp[i-k]*(……),dp[i]已经变成了一个新的值。dp[i+k] = dp[i]*(……),而由于dp[i]已经被新的值覆盖导致错误。
而逆序能保证不出现覆盖
因此01背包是逆序
而完全背包是正序(因为完全背包本身就允许物品覆盖,因为一件物品可以使用无限多次)
四.树形dp
树形dp可以解释为:用子问题的解来解决父问题。
树形dp维护以当前节点为根的树(子树)问题的解。
树形dp要依次遍历当前节点的每个孩子,依次得到每个子树的解。
根据如何处理每个子树,树形dp常见的可以有:
01背包树形dp:每个子树要么选要么不选
分组背包树形dp:当前节点有k个可分配元素,每个子树只能分配一个值,返回一个值
普通树形dp:每个子树得到的解都要,当遍历到第i个孩子(子树)时,之前所有子树的解记录在dp[i-1][……]中
五.状态dp
用二进制表示状态,用状态递推。
注意开始要选好对应顺序,可以正序也可以逆序,个人习惯用逆序。
原数组:0111,正序:7(0111),逆序:14(1110)。
状态dp常用函数:
注意:以下所有函数都是逆序对应的,且标号从0开始
//以下所有函数都是逆序对应的,且标号从0开始
//修改某一位
int change(int x,int i,int k){
x=x&(~(1<<i));//将第i为设为0
x = x | (k << i); // 将第i位设为k
return x;
}
//判断当前状态是否存在相隔的距离小于k的1
//k==1时,就是判断是否存在相邻的1
bool judge(int x,int k){
return !(x&(x<<k))&&!(x&(x>>k));
}
//得到某一位的状态
int get(int x,int k){
return (x>>k)&1;
}
轮廓线dp
原理
假设n*m网格,来到(i,j)
状态信息的(0,j-1)表示i行的决策
状态信息的(j,m-1)才表示i-1行的决策
可以形象的想成:有一条轮廓线将状态分为新状态和老状态,同时轮廓线不断前进,新状态将不断替换老状态。
通过递推轮廓线的方式,轮廓线dp时间上优于普通状态dp
时间复杂度
状态dp需要穷举所有上一状态以及所有可能的当前状态,因此时间复杂度为
O(2^m*2^m*f(x))
m是状态的位数,f(x)是多项式函数
而轮廓线dp在穷举所有上一状态后,不需要穷举所有可能的当前状态,而是递推轮廓线,仅需要遍历状态的每一位,时间复杂度为
O(2^m*m*f(x))
m是状态的位数,f(x)是多项式函数
空间复杂度
轮廓线dp比普通状态dp多出一维,用于记录轮廓线的位置。
经常需要用滚动数组优化空间。
本文介绍动态规划的三种主要类型:区间DP、背包DP及DP优化方法。详细解释了每种类型的适用场景、时间复杂度,并通过实例说明如何进行状态定义与转移方程设定。
1308

被折叠的 条评论
为什么被折叠?



