题目地址:
https://www.acwing.com/problem/content/11/
有 N N N件物品和一个容量是 V V V的背包。每件物品只能使用一次。第 i i i件物品的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最优选法的方案数。注意答案可能很大,请输出答案模 1 0 9 + 7 10^9+7 109+7的结果。
输入格式:
第一行两个整数, N N N, V V V,用空格隔开,分别表示物品数量和背包容积。接下来有 N N N行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 i i i件物品的体积和价值。
输出格式:
输出一个整数,表示方案数模 1 0 9 + 7 10^9+7 109+7的结果。
数据范围:
0 < N , V ≤ 1000 0<N,V≤1000 0<N,V≤1000
0 < v i , w i ≤ 1000 0<v_i,w_i≤1000 0<vi,wi≤1000
思路是动态规划,具体方法是两个状态同时递推。设 f [ i ] [ j ] f[i][j] f[i][j]是只在前 i i i个物品里选,并且总体积恰好是 j j j的最大价值(这里取恰好的原因是让方案数的划分更加清晰,最后方案就可以按照总体积来划分)。那么 f f f的递推方程是: f [ i ] [ j ] = max { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] + w i } f[i][j]=\max\{f[i-1][j],f[i-1][j-v_i]+w_i\} f[i][j]=max{ f[i−1][j],f[i−1][j−vi]+wi}初始条件 f [ 0 ] [ 0 ] = 0 , f [ 0 ] [ . > 0 ] = − ∞ f[0][0]=0,f[0][.>0]=-\infty f[0][0]=0,f[0][.>0]=−∞,表示该状态转移过来的答案应该舍弃。当把 f [ N ] [ V ] f[N][V] f[N][V]递推出来以后,这个 f [ N ] [ V ] f[N][V] f[N][V]一定是从 f [ 0 ] [ 0 ] f[0][0] f[0][0]沿着某条路径走过来的,具体路径需要靠 f [ N ] [ V ] f[N][V] f[N][V]反向推回去,看 max \max max函数取的是谁。题目要求解的,就是路径的总条数。设 g [ i ] [ j ] g[i][j] g[i][j]是上述从 ( 0 , 0 ) (0,0) (0,0)出发走到 ( i , j ) (i,j) (i,j)这个位置为止的路径总条数(即只取前 i i i个物品,总体积恰好是 j j j并且总价值是 f [ i ] [ j ] f[i][j] f[i][j]的方案数)。而 g g g的递推方程要根据 f f f的递推来做: g [ i ] [ j ] = { g [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] > f [ i − 1 ] [ j − v i ] + w i g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j − v i ] + w i > f [ i − 1 ] [ j ] g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j ] = f [ i − 1 ] [ j − v i ] + w i g[i][j]=\begin{cases}g[i-1][j],f[i-1][j]> f[i-1][j-v_i]+w_i \\g[i-1][j-v_i], f[i-1][j-v_i]+w_i> f[i-1][j] \\g[i-1][j]+g[i-1][j-v_i], f[i-1][j]= f[i-1][j-v_i]+w_i\end{cases} g[i][j]=⎩⎪⎨⎪⎧g[i−1][j],f[i−1][j]>f[i−1][j−vi]+wig[i−1][j−vi],f[i−1][j−vi]+wi>f[i−1][j]g[i−1][j]+g[i−1][j−vi],f[i−1][j]=f[i−1][j−vi]+wi初始条件 g [ 0 ] [ 0 ] = 1 g[0][0]=1 g[0][0]=1,最后要返回的答案是: ∑ f [ n ] [ j ] = max k f [ n ] [ k ] g [ n ] [ j ] \sum_{f[n][j]=\max_k f[n][k]} g[n][j] f[n][j]=maxkf[n][k]∑g[n][j]可以只开一维数组做空间优化,但是与 0 − 1 0-1 0−1背包类似,体积要从大到小循环。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];
int main() {
cin >> n >> m;
memset(f, -0x3f, sizeof f);
f[0] = 0;
g[0] = 1;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j--) {
int maxv = max(f[j], f[j - v] + w);
int cnt = 0;
// 看maxv是从哪里转移过来的,累加方案数
if (maxv == f[j]) cnt += g[j];
if (maxv == f[j - v] + w) cnt += g[j - v];
g[j] = cnt % mod;
f[j] = maxv;
}
}
int res = 0;
for (int i = 0; i <= m; i++) res = max(res, f[i]);
int cnt = 0;
for (int i = 0; i <= m; i++)
if (f[i] == res)
cnt = (cnt + g[i]) % mod;
cout << cnt << endl;
return 0;
}
时间复杂度 O ( N V ) O(NV) O(NV),空间 O ( V ) O(V) O(V)。
当然如果直接采用 0 − 1 0-1 0−1背包的状态表示也是可以做的,设 f [ i ] [ j ] f[i][j] f[i][j]是只在前 i i i个物品里选,并且总体积不超过 j j j的最大价值。那么 f f f的递推方程是: f [ i ] [ j ] = max { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v i ] + w i } f[i][j]=\max\{f[i-1][j],f[i-1][j-v_i]+w_i\} f[i][j]=max{ f[i−1][j],f[i−1][j−vi]+wi}初始条件 f [ 0 ] [ j ] = 0 f[0][j]=0 f[0][j]=0。设 g [ i ] [ j ] g[i][j] g[i][j]是上述从 ( 0 , 0 ) (0,0) (0,0)出发走到 ( i , j ) (i,j) (i,j)这个位置为止的路径总条数(即只取前 i i i个物品,总体积不超过 j j j并且总价值是 f [ i ] [ j ] f[i][j] f[i][j]的方案数)。而 g g g的递推方程也是: g [ i ] [ j ] = { g [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] > f [ i − 1 ] [ j − v i ] + w i g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j − v i ] + w i > f [ i − 1 ] [ j ] g [ i − 1 ] [ j ] + g [ i − 1 ] [ j − v i ] , f [ i − 1 ] [ j ] = f [ i − 1 ] [ j − v i ] + w i g[i][j]=\begin{cases}g[i-1][j],f[i-1][j]> f[i-1][j-v_i]+w_i \\g[i-1][j-v_i], f[i-1][j-v_i]+w_i> f[i-1][j] \\g[i-1][j]+g[i-1][j-v_i], f[i-1][j]= f[i-1][j-v_i]+w_i\end{cases} g[i][j]=⎩⎪⎨⎪⎧g[i−1][j],f[i−1][j]>f[i−1][j−vi]+wig[i−1][j−vi],f[i−1][j−vi]+wi>f[i−1][j]g[i−1][j]+g[i−1][j−vi],f[i−1][j]=f[i−1][j−vi]+wi初始条件 g [ 0 ] [ j ] = 1 g[0][j]=1 g[0][j]=1,最后要返回的答案是: g [ n ] [ V ] g[n][V] g[n][V]。也可以只开一维数组做空间优化。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];
int main() {
cin >> n >> m;
for (int i = 0; i <= m; i++) g[i] = 1;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j--) {
int maxv = max(f[j], f[j - v] + w);
int cnt = 0;
if (maxv == f[j]) cnt += g[j];
if (maxv == f[j - v] + w) cnt += g[j - v];
g[j] = cnt % mod;
f[j] = maxv;
}
}
cout << g[m] << endl;
return 0;
}
时空复杂度一样。