开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
看完如果还有疑问,评论区喷我
[doge](狗头保命) ,如果没有疑问的话,别忘了点赞噢!!!
动态规划问题解决方法是拆解成小问题来解决。
01背包通俗的理解
假设你有一个女朋友,你的女朋友让你买东西(她跟你说:要最贵的),背包可以装体积为 4m³ 的物品。求用这个背包最多能装东西的最大价值。 下面有三件物品: | 物品名称 | 香水 | 眉笔 | 口红 | |--|--|--|--|--| | 价格(元) | ¥3 | ¥2 | ¥1.5 | | 体积(dm³) | 4 | 3 | 1 |
现在让你选择,用这个背包装走价值最高的物品,应该如何选择?
暴力法 (不会吧不会吧还有人用暴力法???)
好吧,那就暴力一下? 所有的选法如下:
| 序号 | 物品名称 | 总价格 | 体积(m³) | 是否能装得下 |
|---|---|---|---|---|
| ① | 无 | ¥0 | V = 0m³ | × |
| ② | 香水 | ¥3 | V = 4m³ | √ |
| ③ | 眉笔 | ¥2 | V = 3m³ | √ |
| ④ | 口红 | ¥1.5 | V = 1m³ | √ |
| ⑤ | 香水 + 口红 | ¥4.5 | V = 5m³ | × |
| ⑥ | 香水 + 眉笔 | ¥5 | V = 7m³ | × |
| ⑦ | 眉笔 + 口红 | ¥3.5 | V = 4m³ | √ |
| ⑧ | 香水 + 眉笔 + 口红 | ¥6.5 | V = 8m³ | × |
暴力的方法:很明显 —— 很慢 只有三个物品,就要考虑 8 种可能的情况。 如果有 n 件物品,就有 种可能的情况。 时间复杂度为: !!!!
这不行这不行,受不了受不了,太慢了。
所以引入了动态规划的思想。
动态规划法(难道还有人不用动态规划???)
动态规划的核心思想:先解决子问题,再一步一步最终解决大问题。
那么,对于这个背包问题,我们可以先拆成小背包。
首先看第一行,意思就是,现在要将口红装入背包里面。
换句话说:假装现在 你女朋友 目前只让你买口红。(如果有例外的话,那就是你不听你女朋友的话,那我无话可说)
当体积为1的时候:口红
口红的体积为:1m³
那么依次有:
①这个小背包有 1m³ 可以装得下口红 (1m³)
② 这个小背包有 2m³ 可以装得下口红 (1m³)
③ 这个小背包有 3m³ 可以装得下口红
④ 这个小背包有 4m³ 可以装得下口红
好的!看第二行,现在 你女朋友 允许你装香水了。
开始装香水整个过程为:
当背包容量为4的时候,可以装入香水(香水的体积为:4m³) 则更新最大价值为:¥3。
当你女朋友叫你装眉笔的时候:
一直到最终解,逐步修改最大值。
核心思想
dp[i, j] = max(上一个单元格的值 dp[i - 1][j] ,当前商品的价值 + 剩余空间的价值 dp[i-1][j - v[i]] + w[i])
原理
题意: 每件物品仅用一次,共N件物品,容量为V的背包。每件物品只能用一次。
将f[i]表示的所有选法分成两大类(划分原则:不漏)
- 选法中不含i,即从1~i-1中选,且总体积不超过j,即“f[i-1][j]”
- 选法中包含i,即从1~i中选,包含i,且总体积不超过“j”,即"f[i][j]"
可以先把第i个物品拿出来,即从第1~i-1中选,且总体积不超过“j-v[i]”
即:f[i-1][j-v[i]]+w[i];
所以:f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
例题
总共: 4件物品且背包容量为5
| 物品编号 | 体积 | 价值 |
|---|---|---|
| Ⅰ | 1 | 2 |
| Ⅱ | 2 | 4 |
| Ⅲ | 3 | 4 |
| Ⅳ | 4 | 5 |
则应该选,中间两样东西,体积之和为5,价值之和为8
最朴素的做法(二维空间)
一维表示价值,一维表示体积,两重for循环
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
}
可优化的点
可以观察得出:
f[i]只用到了f[i-1]这一层,即f[i-2]到f[0]对f[i]是完全没有用的
所以第二层循环可以直接从v[i]开始
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
从二维优化成一维过程如下图
如果直接删除掉f[i]这一维即f[j]=max(f[j],f[j-v[i]]+w[i]);
如果删掉f[i]这一维,结果如下:(如果j层循环是递增的,则是错误的)
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
证明:j层循环是递增的,则是错误的
原式:f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
改成一维:f[j]=max(f[j],f[j-v[i]]+w[i]);
由于f[i][]只跟上一状态(f[i-1][])有关 上面两个式子 :这一状态(左)=上一状态(右)
即f[i][j]是由f[i-1][j-v[i]]推出来的现在进行空间优化,那么必须要保证f[j]要由f[j-v[i]]推出来的。
如果j层循环是递增的,则相当于f[i][j]变得是由f[i][j-v[i]]推出来的,而不是f[i-1][j-v[i]]推出来的。
优化过程数值全模拟
例子:假设有3件物品,背包的总体积为10
| 物品编号 | 体积 | 价值 |
|---|---|---|
| Ⅰ | 4 | 5 |
| Ⅱ | 5 | 6 |
| Ⅲ | 6 | 7 |
因为f[0][j]总共0件物品,所以最大价值为0,即f[0][j]==0成立
如果 j 层循环是递增的:
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
当还未进入循环时:
f[0]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=0;
当进入循环i==1时:
f[4]=max(f[4],f[0]+5);即max(0,5)=5;即f[4]=5;
f[5]=max(f[5],f[1]+5);即max(0,5)=5;即f[5]=5;
f[6]=max(f[6],f[2]+5);即max(0,5)=5;即f[6]=5;
f[7]=max(f[7],f[3]+5); 即max(0,5)=5;即f[7]=5;
!!!!!!!!!!!!!重 点 来 了!!!!!!!!!!!!!!!!
f[8] = max(f[8], f[4] + 5); 即max(0, 5 + 5) = 10; 即f[8] = 10;(这里的f[4]已经被f[0]更新过了,所以×)
这里就已经出错了,因为此时处于i==1这一层,即物品只有一件,不存在单件物品满足价值为10
所以已经出错了。
如果j层循环是逆序的:
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
数值模拟过程如下:
1. 还未进入循环
f[0] = 0; f[1] = 0; f[2] = 0; f[3] = 0; f[4] = 0;
f[5] = 0; f[6] = 0; f[7] = 0; f[8] = 0; f[9] = 0; f[10] = 0;
2. 当进入循环 i==1
w[i] = 5; v[i] = 4;
j=10:f[10]=max(f[10],f[6]+5);即max(0,5)=5;即f[10]=5;
j=9:f[9]=max(f[9],f[5]+5);即max(0,5)=5;即f[9]=5;
j=8:f[8]=max(f[8],f[4]+5);即max(0,5)=5;即f[8]=5;
j=7:f[7]=max(f[7],f[3]+5);即max(0,5)=5;即f[7]=5;
j=6:f[6]=max(f[6],f[2]+5);即max(0,5)=5;即f[6]=5;
j=5:f[5]=max(f[5],f[1]+5);即max(0,5)=5;即f[5]=5;
j=4:f[4]=max(f[4],f[0]+5);即max(0,5)=5;即f[4]=5;
3. 当进入循环 i==2:
w[i]=6;v[i]=5;
j=10:f[10]=max(f[10],f[5]+6);即max(5,11)=11;即f[10]=11;
j=9:f[9]=max(f[9],f[4]+6);即max(5,11)=5;即f[9]=11;
j=8:f[8]=max(f[8],f[3]+6);即max(5,6)=6;即f[8]=6;
j=7:f[7]=max(f[7],f[2]+6);即max(5,6)=6;即f[7]=6;
j=6:f[6]=max(f[6],f[1]+6);即max(5,6)=6;即f[6]=6;
j=5:f[5]=max(f[5],f[0]+6);即max(5,6)=6;即f[5]=6;
4. 当进入循环 i==3:
w[i]=7;v[i]=6;
j=10:f[10]=max(f[10],f[4]+7);即max(11,12)=12;即f[10]=12;
j=9:f[9]=max(f[9],f[3]+6);即max(11,6)=11;即f[9]=11;
j=8:f[8]=max(f[8],f[2]+6);即max(6,6)=6;即f[8]= 6;
j=7:f[7]=max(f[7],f[1]+6);即max(6,6)=6;即f[7]=6;
j=6:f[6]=max(f[6],f[0]+6);即max(6,6)=6;即f[6]=6;
就模拟一下发现没有错误,即逆序就可以解决这个优化的问题了
优化后的代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}
我猜看到这里的人应该不多,但是既然都看到这里了,不管文章写的o不ok,有没有忘记点赞???