题目链接: 01 背包
题意: 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi ,把它放进袋子里会获得 wi 的收益,每种物品至多能用一次,问如何选择物品,使得在物品的总体积不超过 m 的情况下,获得最大的收益?请求出最大收益。
输入格式:
第一行两个整数 n, m。
接下来 n 行,每行两个整数vi, wi。
输出格式:
一个整数,表示答案。
样例输入:
5 10
5 3
3 6
7 8
5 9
2 4
样例输出
19
Step1 :
思路分析:
01 背包问题作为经典的动态规划问题。我们拿到题目该如何考虑呢?首先我们要考虑该如何表示各种状态,通过对题目分析可以看出我们需要记录的信息有取到第
i
i
i 个物品,取到第
i
i
i 个物品时的总体积,以及此时的最大收益。因此我们可以使用
f
[
i
,
j
]
f [i, j]
f[i,j] 来表示取到第
i
i
i 个物品时(取到而不是取了),总体积为
j
j
j。同时
f
[
i
]
[
j
]
f[i][j]
f[i][j] 的值表示此时能获得的最大收益。
接下来我们考虑状态之间是如何转移的。显然对于第
i
i
i 件物品我们有取和不取两种选择,如果不取第
i
i
i 件物品,那么此时的最大收益
f
[
i
]
[
j
]
f[i][j]
f[i][j] 就是取到前
i
−
1
i - 1
i−1 物品体积为
j
j
j 的最大收益,即
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]。如果我们取第
i
i
i 件物品,此时的最大收益就是取到前
i
−
1
i - 1
i−1 个物品体积为
j
−
v
[
i
]
j - v[i]
j−v[i] 的最大收益加上第
i
i
i 件物品的收益,即
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
f[i-1][j - v[i]] + w [i]
f[i−1][j−v[i]]+w[i]。综上,取到第
i
i
i 个物品,总体积为
j
j
j 时的最大收益
f
[
i
]
[
j
]
f[i][j]
f[i][j] 即为上面两个情况收益的较大者,所以我们得到的状态转移方程如下:
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
1
]
[
j
]
不取第
i
个物品
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
取第
i
个物品
f [i][j]=max\begin{cases} f [i-1][j] & 不取第 i 个物品 \\ f[i - 1][j - v[i]] + w[i] & 取第 i 个物品 \end{cases}
f[i][j]=max{f[i−1][j]f[i−1][j−v[i]]+w[i]不取第i个物品取第i个物品
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int N = 1005;
int n, m;
int v[N], w[N], f[N][N];
int main()
{
IO;
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 = 0; j <= m; ++ j)
if(j < v[i]) // 空间不够只能不取
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
cout << f[n][m] ;
return 0;
}
Step2 :
优化思路 :
通过Step 1我们实现了对01 背包问题的求解,但我们发现记录状态(即
f
[
N
]
[
N
]
f[N][N]
f[N][N])所用的空间较大,那么我们在解决问题的过程中真的要使用这么大的空间吗?我们通过分析状态转移方程可知:第
i
i
i 个的状态只和第
i
−
1
i - 1
i−1 个状态有关,如下图所示:
所以我们便得到了优化思路:使用
g
g
g 数组记录本轮的状态,用
f
f
f 数组记录上一轮状态,本轮更新完成后再将
f
f
f 数组更新为
g
g
g 数组,如此往复便可以求解出最终答案。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int N = 1005;
int n, m;
int v[N], w[N], f[N], g[N];
int main()
{
IO;
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 = 0; j <= m; ++ j)
{
if(j < v[i])// 空间不够只能不取
g[j] = f[j];
else
g[j] = max(f[j], f[j - v[i]] + w[i]);
}
memcpy(f, g, sizeof(g)); // 用 g 更新 f
}
cout << f[m] ;
return 0;
}
Step3 :
优化思路:
通过Step2我们已经将空间优化到只需要用到
f
f
f 和
g
g
g 两个数组,那么我们能不能再优化一步,只用一个
f
f
f 数组记录就可以呢?在这里我们再画个图看一看:
如果我们只使用数组
f
f
f,我们在更新第
i
i
i 轮时,
f
f
f 数组中存储的是
i
−
1
i - 1
i−1的信息,而我们通过图可以看出,在更新第
i
i
i 轮的第
j
j
j 个位置时,使用的是第
i
−
1
i-1
i−1 轮的第
j
j
j 个位置或者第
j
−
v
[
i
]
j-v[i]
j−v[i] 个位置,显然就是此时的
f
[
j
]
f[j]
f[j] 和
f
[
j
−
v
[
i
]
]
f[j-v[i]]
f[j−v[i]],显然这种优化的思路是可行的。但需要注意一点,在更新第
i
i
i 轮时,
j
j
j 需要从大到小去枚举体积。为什么呢?如果我们从小到大去进行枚举,假如我们已经更新了第
j
−
v
[
i
]
j-v[i]
j−v[i] 这个位置,那么在更新
j
j
j 这个位置时,我们需要使用
j
−
v
[
i
]
j-v[i]
j−v[i] 这个位置第
i
−
1
i-1
i−1 轮的信息,但此时
j
−
v
[
i
]
j-v[i]
j−v[i] 这个位置上的信息已经是第
i
i
i 轮的信息,显然这样操作我们得到的是错误的答案。通过上面的分析,我们便完成了更近一步的优化。
参考代码:
#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int N = 1005;
int n, m;
int v[N], w[N], f[N];
int main()
{
IO;
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)
// 那么为什么枚举到v[i]就可以了???
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] ;
return 0;
}
End:
01 背包问题的求解分享就到这里结束啦,如果对大家有帮助的话,大家一定要点赞 + 收藏哦!!!