-------------------------------------------------------本篇文章尚未完结,大家可以先看已有部分-------------------------------------------------------
【DP动态规划】学习笔记大全
- Part 1 背包DP
- 1.1 01背包
- 1.1.1 题意解释
- 1.1.2 为什么不使用贪心
- 1.1.3 该如何进行 01DP
- 1.1.4 推荐例题
- 1.1.5 优化方式
- 1.2完全背包
- 1.2.1 题意解释
- 1.2.2 如何进行状态转移
- 1.2.3 推荐例题
- Part 2 区间DP
- 2.1典例引入
- 2.2 基本概念
- 2.3 例题分析
- 2.3.1 题意解释
- 2.3.2 环的处理
- 2.3.3 题目思路
- 2.3.4 主要代码
- 2.4 推荐例题
Part 1 背包DP
1.1 01背包
1.1.1 题意解释
你有一个容量为 n n n 的背包,一共有 m m m 种物品,每个物品都有一个权值 v i v_i vi 和体积 w i w_i wi,在不超过背包容量的前提下,尽可能装权值最多的东西
1.1.2 为什么不使用贪心
考虑到本题,没学过DP的人可能会用以下两种贪心:
- 装权值越大的东西:(错误)
i i i | 1 | 2 | 3 |
---|---|---|---|
v i v_i vi | 4 | 3 | 2 |
w i w_i wi | 70 | 69 | 1 |
对于上面这一组数据,当 n = 70 n = 70 n=70 时如果我们使用这样的贪心策略,会优先选择体积为 70 70 70,权值为 4 4 4 的物品,但明显的,选择另外两个会更优。
- 计算平均值,在许可范围内选择平均值最大的
i i i | 1 | 2 | 3 |
---|---|---|---|
v i v_i vi | 150 | 100 | 100 |
w i w_i wi | 100 | 75 | 74 |
v i w i \frac{v_i}{w_i} wivi | 3 2 \frac{3}{2} 23 | 4 3 \frac{4}{3} 34 | 50 37 \frac{50}{37} 3750 |
明显的,对于上述样例,这种方法依然得不到正确答案。
1.1.3 该如何进行 01DP
我们设 d p i , j dp_{i,j} dpi,j 表示当前取到了第 i i i 个物品, 还剩 j j j 点容量时的最大值,那么我们即可得到一个转移方程:
d p i , j = m a x ( d p i − 1 , j − w i + v i , d p i − 1 , j ) ; dp_{i,j} = max(dp_{i - 1,j - w_i}+v_i,dp_{i - 1,j}); dpi,j=max(dpi−1,j−wi+vi,dpi−1,j);
换一句话说,这就表示“把第 i i i 个,第 i + 1 i+1 i+1个,…, n n n个物品装到容量为 j j j 的背包中的最大权值”。
那么我们根据以上转移方程就可以得到以下代码:
for(int i=1;i<=m;i++) {
for(int j=t;j>=0;j--) {
if(j>=w[i])
dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]);
else
dp[i][j]=dp[i-1][j];
}
}
明显的,答案就是 d p m , n dp_{m,n} dpm,n。
1.1.4 推荐例题
P1048 [NOIP2005 普及组] 采药
P1049 [NOIP2001 普及组] 装箱问题
P2871 [USACO07DEC] Charm Bracelet S
P1060 [NOIP2006 普及组] 开心的金明
P1164 小A点菜
1.1.5 优化方式
对于 01背包,因为 DP 的无后效性,所以我们可以考虑用滚动数组进行优化,那么这个转移方程就变成了:
f j = m a x ( f j , f j − v i + w i ) f_j = max(f_j,f_j−v_i+w_i) fj=max(fj,fj−vi+wi)
这样就可以省下大幅的空间。
注意:使用滚动数组时要倒序遍历
1.2完全背包
1.2.1 题意解释
- 完全背包与 01背包类似,只不过每个物品可以选择无限多次。
1.2.2 如何进行状态转移
因此我们考虑任然使用 01 背包的状态设计,但明显的是,01背包的状态转移已经不适用于完全背包。
我们发现,对于
d
p
i
,
j
dp_{i,j}
dpi,j,只要从
d
p
i
,
j
−
w
i
dp_{i,j - w_i}
dpi,j−wi转移即可,这样满足了DP的无后效性,那么我们可以得到以下的状态转移公式:
d p i , j = m a x ( d p i − 1 , j − w i + v i , d p i − 1 , j ) ; dp_{i,j} = max(dp_{i - 1,j - w_i}+v_i,dp_{i - 1,j}); dpi,j=max(dpi−1,j−wi+vi,dpi−1,j);
同样的,完全背包也可以像 01背包一样压缩掉一维,优化空间复杂度,同时,在压缩之后的转移顺序为正序,由此我们可以得到以下代码:
for (int i = 1; i <= n; i++)
for (int l = w[i]; l <= m; l++)
if (f[l - w[i]] + v[i] > f[l]) f[l] = f[l - w[i]] + v[i];
1.2.3 推荐例题
P1616 疯狂的采药
P2722 [USACO3.1] 总分 Score Inflation
P1679 神奇的四次方数
P1832 A+B Problem(再升级)
Part 2 区间DP
2.1典例引入
- 给定长度为 n n n 的序列 a a a,每次可以将其中连续的一段回文子段删去,删去后左右两段将合并,问最少用几次操作可以消除整个区间
- 经观察发现,每个时刻消去的位置总是一个连续的区间。
- 因此我们考虑消去区间 [ i , j ] [i,j] [i,j] 时:
- 若 a i a_i ai 和 a j a_j aj 不在一起消去,那我们总是可以找到一个分界点 k k k ,使得区间 [ i , k ] [i,k] [i,k] 和区间 [ k + 1 , j ] [k + 1,j] [k+1,j] 可以分别消去。
- 若 a i a_i ai 和 a j a_j aj,那他们只需要接在 [ i + 1 , j − 1 ] [i + 1,j - 1] [i+1,j−1] 这段区间后一起消去即可
- 那我们用 f i , j f_{i,j} fi,j 表示消去这段区间需要的最少次数,那我们可以得到以下转移方程:
- { f i , j = m i n ( f i , k + f k + 1 , j ∣ i ≤ k < j ) a i ≠ a j f i , j = m i n ( m i n ( f i , k + f k + 1 , j ∣ i ≤ k < j ) , f i , j , f i + 1 , j − 1 ) a i = a j \begin{cases} f_{i,j} = min(f_{i,k} + f_{k + 1,j}|i \leq k < j) & a_i \ne a_j \\ f_{i,j} = min(min(f_{i,k} + f_{k + 1,j}|i \leq k < j),f_{i,j},f_{i + 1,j - 1}) & a_i = a_j \end{cases} {fi,j=min(fi,k+fk+1,j∣i≤k<j)fi,j=min(min(fi,k+fk+1,j∣i≤k<j),fi,j,fi+1,j−1)ai=ajai=aj
- 我们发现,这种状态转移是以区间为基础的,这种DP通常称作区间DP
- 区间DP的解法一般固定,一般是枚举区间长度,然后枚举左端点,最后枚举区间断点。时间复杂度为 o ( n 3 ) o(n^3) o(n3)。
2.2 基本概念
- 合并:将两个及以上的区间通过一定方式进行整合,也可以反过来操作。
- 特征:能将问题分解成两两合并的形式。
- 求解:对整个问题设最优值,枚举合并点,将问题分解成左右两部分,最后合并两部分的最优解得到原问题的最优解,有点类似于分治的思想。
2.3 例题分析
本处采用石子合并作为例题分析。
2.3.1 题意解释
- 有 n n n 个石子沿着一个环分布,现在要将石子有次序的合成一堆。
- 规定每次只能选择相邻的石子,并将新的一堆的石子数计为该次合并的得分
- 要求得到两种合并策略,使得最后的得分最大和最小
- 1 ≤ n ≤ 200 1 \leq n \leq 200 1≤n≤200
2.3.2 环的处理
考虑到题目中出现了环,那么我们该如何处理呢?
我们可以将链延长两倍,扩展成
2
×
n
−
1
2 \times n - 1
2×n−1堆,其中第
i
i
i 堆与第
n
+
i
n+i
n+i堆完全相同,然后我们再转以后分别枚举
d
p
i
,
n
,
d
p
2
,
n
+
1
,
.
.
.
,
d
p
n
,
2
n
−
1
dp_{i,n},dp_{2,n+1},...,dp_{n,2n-1}
dpi,n,dp2,n+1,...,dpn,2n−1,并取其中的最大值即可。
时间复杂度是
O
(
8
n
3
)
O(8n^3)
O(8n3) 符合本题的要求,一般是区间DP出现环时最好的处理方法
2.3.3 题目思路
- 考虑到如果第 l l l 和第 r r r 堆石子被合并,那么 [ l , r ] [l,r] [l,r] 之间的石子应当也被合并,所以我们可以在任何时刻用 [ l , r ] [l,r] [l,r] 这一个区间来表示任意一堆石子,代表他们是由 [ l , r ] [l,r] [l,r]之间的石子所合并来的。
- 同时也存在一个 k k k,使得 [ l , k ] [l,k] [l,k]和 [ k + 1 , r ] [k+1,r] [k+1,r]之间的石子能分别被合并。
- 那么我们就可以用 f i , j f_{i,j} fi,j 表示从第 i i i 堆石子合并到第 j j j 堆石子的最大(最小)值,通过对左右端点对区间的表示,我们可以得到以下转移方程(以最大值为例):
- f i , j = m a x ( f i , k + f k + 1 , j + s u m j − s u m i − 1 ∣ i ≤ k ≤ j − 1 ) f_{i,j} = max(f_{i,k} + f_{k + 1,j} +sum_j - sum_{i - 1}|i \le k \le j - 1) fi,j=max(fi,k+fk+1,j+sumj−sumi−1∣i≤k≤j−1)
在此处 s u m i sum_i sumi 表示从第 1 1 1 堆石子到第 i i i 堆石子数的总和。
2.3.4 主要代码
for(int p=1;p<n;p++) {
for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p) {
f2[i][j] = 999999999;
for(int k=i;k<j;k++) {
f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
}
}
}
2.4 推荐例题
P1063 [NOIP2006 提高组] 能量项链
P3146 [USACO16OPEN] 248 G
P4767 [IOI2000] 邮局 加强版