题目描述

有 $N$ 件物品和一个容量是 $V$ 的背包。每件物品只能使用一次。

第 $i$ 件物品的体积是 $v_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,$N,V$,用空格隔开,分别表示物品数量和背包容积。

接下来有 $N$ 行,每行两个整数 $v_i,w_i$用空格隔开,分别表示第 $i$ 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

$0 \lt N, V \le 1000$
$0\lt v_i, w_i \le 1000$

输入样例

1
2
3
4
5
4 5
1 2
2 4
3 4
4 5

输出样例:

1
8

题目求解

$f[ i ][ j ] $表示只选前$i$个物品总体积小于等于$j$的最佳方案,01背包的状态转移方程为:

对于第$i$个物品来说可能选也可能不选,因此$f[i][j]$取两者间的较大者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1010;
int f[N][N], v[N], w[N], 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 = 0 ; 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[j]$表示一行的结果,每次循环将$f[j]$当成$f[i \sim 1][j]$的一行数据,然后倒着去计算新一行数据,注意这里不可以正着去计算,因为前面计算出的新一行结果会覆盖上一行的结果导致后面的计算可能出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1010;
int f[N], v[N], w[N], 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;
}

写在最后

$f[j]$数组的初始化问题,上边的代码将$f[i][0 \sim m]$都初始化为了$0$,这样的话$f[m]$就是最大值了,就不需要遍历$f[n][0 \sim m]$去求最大值了。

$f[0][0 \sim m]$都初始化为$0$

这种情况下$f[j]$表示总体积小于等于$j$时候的最优方案,为什么$f[m]$就是最大值了呢?假设$f[k]$是最大值且此时k是恰好装满的体积,那么$f[k]$可以由$f[0]$一步步转移而来,而$f[m]$可以由$f[m \sim k]$一步步转移而来,相当于是有了一个横向的偏移量。这里的$f[0]$和$f[m \sim k]$可以理解为$f[0][0]$和$f[0][m \sim k]$,因此$f[m]$可以按相同的路径达到最大值,即$f[m]$就是最大值。

$f[0][0]$ 初始化为0 $f[0][1 \sim m]$初始化为-INF

此时的$f[j]$或是$f[i][j]$都是表示体积恰好是j的情况,只有$f[0][0] = 0$是合理的,因此如果这样初始化的话,此题目的最大值需要遍历$f[n][0 \sim m]$去求得。