DP模型
DP 的分类
DP问题可以分为线性和非线性的。
-
线性DP,线性DP有两种方法,即顺推(自下向上,常写成循环嵌套的形式)与倒推(自上向下,常写成记忆化搜索)。
-
非线性DP,例如树形DP,建立在树上,也有两个方向:①根→叶,根传递有用的信息给子节点,最后根得出最优解;②叶→根,根的子节点传递有用的信息给根,最后根得到最优解。
NOIP普及组常考的DP类型有如下几种:
- 线性DP: 线性动态规划,即具有线性阶段划分的动态规划。问题的模型是线性的,数据结构表现为线性表(数组)的形式。经典的问题包括:递推、最长公共子序列、最长递增子序列以及01背包问题。
- 背包DP: 背包动态规划是线性动态规划中特殊的一类,NOIP中考到的次数很多。一般用状态 f[i][j]f[i][j] 来表示 1\sim i1∼i 件物品装进容量是 jj 的背包时的 最大价值,背包型DP可以利用滚动数组优化成一维,但时间复杂度仍为 O(n^2)O(n2)。
- 区间DP: 区间动态规划一般以区间作为动态规划的阶段。一般这类问题有个明显的特点:就是给出的问题是按区间的性质进行,要么合并,要么分解为区间操作。一般用状态 dp[l][r]dp[l][r] 来表示从左端点 ll 到右端点 rr 这个区间上问题的 最优解。
- 矩阵DP: 矩阵类型,线性DP中特殊的一类,就是给出的数据模型就是一个二维矩阵,通常,在问题中给出了明确的决策,即在矩阵中进行操作。
DP 的特点
dp特点:dp把原问题视作若干个 重叠 的子问题的逐层递进,每个子问题的求解过程都会构成一个“阶段”,在完成一个阶段后,才会执行下一个阶段。
dp要满足无后效性:已经求解的子问题 不受后续阶段的影响。
DP 的解题要素
- 设计状态:设计dp数组的含义,它要满足无后效性。
- 设计状态转移方程:子问题之间是如何逐层递进的。递推型 DP 是通过一个转移方程实现的,记忆化搜索是通过递归求解子问题实现的。
背包问题
给定 nn 个重量为 w_1,w_2,...,w_nw1,w2,...,wn ,价值为 v_1,v_2,...,v_nv1,v2,...,vn 的物品,要放进一个只能装最大重量为 CC 的背包,问,如何选择物品放入,才能使这个背包内的物品的总价值最大?求这个最大价值。
值得注意的是: 01背包问题是所有背包类问题的最基础的模型,其他类型的背包问题都可以通过将 01 背包的模型进行一些转化后得到。因此,01 背包一定要牢固掌握。
01背包
限制条件:每种物品只有一件,可以选择装(一件)或不装(装零件)。
定义状态
- 定义 f[i][j]f[i][j] 来表示将前 ii 件物品放入容量为 jj 的背包中时,所能获得的最大价值。容易得到,目标状态(我们最终要求的答案)就是 f[n][C]f[n][C]。
状态转移方程
- 考虑第 ii 件物品:
- 不放:f[i][j] = f[i-1][j]f[i][j]=f[i−1][j]
- 放:f[i][j] = f[i-1][j-w_i] + v_i, j-w_i>=0f[i][j]=f[i−1][j−wi]+vi,j−wi>=0
- 最终方程:f[i][j] = max(f[i-1][j], f[i-1][j-w_i] + v_i)f[i][j]=max(f[i−1][j],f[i−1][j−wi]+vi)
- 可以优化掉状态的第一维,来降低空间复杂度
const int N = 1e4+5;
int n, m, w[N], v[N], dp[N]; // dp[i]: 装满体积是 i 的背包,能装的最大价值
int main(){
cin >> m >> n; // m: 背包体积 n:物品数量
for(int i = 1; i <= n; i++)
cin >> w[i] >> v[i]; // 物品重量 和 价值
for(int i = 1; i <= n; i++) // 枚举物品
for(int j = m; j >= w[i]; j--) // 倒序枚举体积
dp[j] = max(dp[j-w[i]]+v[i], dp[j]);
cout << dp[m];
return 0;
}
Copy<