题目
n
n
n个物品,重量和价值分别存储在数组
w
w
w和数组
v
v
v中,输入总重量
W
W
W,求选出不超过
W
W
W的物品最大总价值。
每个物品只能选择一次。
n , w , v n,w,v n,w,v,均为 1 ~ 100 1~100 1~100, W W W为 1 ~ 10000 1~10000 1~10000.
样例输入
4
2 1 3 2
3 2 4 2
5
n = 4 n=4 n=4个物品,重量 w w w分别是 2 , 1 , 3 , 2 2,1,3,2 2,1,3,2,价值 v v v分别是 3 , 2 , 4 , 2 3,2,4,2 3,2,4,2,背包重量 W W W为 5 5 5.
样例输出
7
选出 0 , 1 , 3 0,1,3 0,1,3号物品,刚好装满背包,且总价值最大,为 7 7 7.
算法1
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示从前
i
i
i个物品(
0
~
i
−
1
0~i-1
0~i−1号)中选出重量不超过
j
j
j的总价值。如果不选择第
i
i
i个物品,则
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i−1][j];如果选择第
i
i
i个物品,则
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
w
[
i
−
1
]
]
+
v
[
i
−
1
]
dp[i][j]=dp[i-1][j-w[i-1]]+v[i-1]
dp[i][j]=dp[i−1][j−w[i−1]]+v[i−1](不过要注意判断
j
≥
w
[
i
−
1
]
j≥w[i-1]
j≥w[i−1],如果不成立说明背包装不下第
i
i
i个物品),这样可得递推关系:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
]
[
j
]
=
m
a
x
{
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
[
i
−
1
]
]
+
v
[
i
−
1
]
}
,
j
≥
w
[
i
−
1
]
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
,
j
<
w
[
i
]
dp[i][j]=\left\{ \begin{aligned} dp[i][j]=max\{dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]\},j≥w[i-1]\\ dp[i][j]=dp[i-1][j],j<w[i] \end{aligned} \right.
dp[i][j]={dp[i][j]=max{dp[i−1][j],dp[i−1][j−w[i−1]]+v[i−1]},j≥w[i−1]dp[i][j]=dp[i−1][j],j<w[i]
其中初始值为
d
p
[
0
]
[
j
]
=
0
dp[0][j]=0
dp[0][j]=0。
最终输出 d p [ n ] [ W ] dp[n][W] dp[n][W]。
由于要从 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]更新到 d p [ n ] [ W ] dp[n][W] dp[n][W],所以复杂度为 O ( n W ) O(nW) O(nW)。
代码1
#include<stdio.h>
#include<math.h>
#define maxn 105
int max(int a, int b){
return (a>b)?a:b;
}
int main(){
int n, W;
int w[maxn], v[maxn];
int dp[maxn][maxn];
while(scanf("%d %d", &n, &W)==2){
for(int i = 1;i <= n;i++){
scanf("%d", w + i);
}
for(int i = 1;i <= n;i++){
scanf("%d", v + i);
}
for(int j = 0;j <= W;j++){
dp[0][j] = 0;
}
for(int i = 1;i <= n;i++){
dp[i][0] = 0;
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= W;j++){
if(w[i]>j)dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
}
}
printf("%d\n", dp[n][W]);
}
return 0;
}
算法2
如果改变题目对输入数据的约束:
n
,
v
n,v
n,v,均为
1
~
100
1~100
1~100,
w
w
w为
1
~
1
0
7
1~10^7
1~107,
W
W
W为
1
~
1
0
9
1~10^9
1~109.
已知复杂度为 O ( n W ) O(nW) O(nW),而上述W非常大,这会导致复杂度很高。
不妨改变 d p [ i ] [ j ] dp[i][j] dp[i][j]为前 i i i个物品( 0 ~ i − 1 0~i-1 0~i−1号)中选出价值为 j j j的最小重量,则需要从 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]更新到 d p [ n ] [ 所 有 物 品 总 价 值 ] dp[n][所有物品总价值] dp[n][所有物品总价值],然后输出满足 d p [ n ] [ j ] < W dp[n][j]<W dp[n][j]<W的 j j j的最大值。所有物品的总价值不会超过 Σ i = 0 i = n − 1 v [ i ] < = 10000 \Sigma_{i=0}^{i=n-1}v[i]<=10000 Σi=0i=n−1v[i]<=10000,复杂度就降下来了。
如果不选择第i个物品,则dp[i][j]对应从前
i
−
1
i-1
i−1个物品中选出价值为
j
j
j,即
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j];如果选择第
i
i
i个物品,则
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]对应
d
p
[
i
−
1
]
[
j
−
v
[
i
−
1
]
]
+
w
[
i
−
1
]
dp[i-1][j-v[i-1]]+w[i-1]
dp[i−1][j−v[i−1]]+w[i−1](记得先判断
j
≥
v
[
i
−
1
]
j≥v[i-1]
j≥v[i−1]),这样得到递推关系:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
]
[
j
]
=
m
i
n
{
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
v
[
i
−
1
]
]
+
w
[
i
−
1
]
}
,
j
≥
v
[
i
−
1
]
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
,
j
<
v
[
i
]
dp[i][j]=\left\{ \begin{aligned} dp[i][j]=min\{dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]\},j≥v[i-1]\\ dp[i][j]=dp[i-1][j],j<v[i] \end{aligned} \right.
dp[i][j]={dp[i][j]=min{dp[i−1][j],dp[i−1][j−v[i−1]]+w[i−1]},j≥v[i−1]dp[i][j]=dp[i−1][j],j<v[i]
初始值为
d
p
[
0
]
[
0
]
=
0
dp[0][0]=0
dp[0][0]=0,
d
p
[
0
]
[
非
零
]
=
I
N
F
dp[0][非零]=INF
dp[0][非零]=INF。输出满足
d
p
[
n
]
[
j
]
<
W
dp[n][j]<W
dp[n][j]<W的
j
j
j的最大值。
#include<stdio.h>
#define maxn 105
#define INF 1000000005 // 所有物品总重最多为10亿
#define maxv 105
int min(int a, int b){
return (a<b)?a:b;
}
int main(){
int n, W;
int v[maxn], w[maxn];
int dp[maxn][maxn * maxv];
int totalvalue, res;
while(scanf("%d", &n)==1){
for(int i = 0;i < n;i++){
scanf("%d", &w[i]);
}
totalvalue = 0;
for(int i = 0;i < n;i++){
scanf("%d", &v[i]);
totalvalue += v[i];
}
scanf("%d", & W);
for(int j = 1;j <= totalvalue;j++){
dp[0][j] = INF;
}
dp[0][0] = 0;
for(int i = 1;i <= n;i++){
for(int j = 0;j <= totalvalue;j++){
if(j>=v[i-1]){
dp[i][j] = min(dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]);
}
else dp[i][j] = dp[i-1][j];
}
}
// 输出dp[n][j]中不超过W的最大的j
res = 0;
for(int j = totalvalue;j >= 0;j--){
if(dp[n][j]<=W){
res = j;
break;
}
}
printf("%d\n", res);
}
return 0;
}