题目描述
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
输入格式
第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2~N+1行:每行二个整数Wi、Ci,表示每个物品的重量和价值。
输出格式
仅一行,一个数,表示最大总价值。
样例输入
10 4
2 1
3 3
4 5
7 9
样例输出
12
题解:
该问题与01背包不同点在于,该问题中每个物品的数量有无限个
1.转换成01背包
dp [ i ][ j ] = max(dp[ i-1 ][ j ],dp[ i - 1][ j-w[i] ] +c[ i ])
最容易想到的一个方法就是将完全背包装换成01背包。
(1)实际上,最简单的一种实现就是把每个物品复制W/w[i]份,这样把同类的多份都视作01背包中最多只能选择1次的物品,即可转换成01背包。不过这个实现空间占用较多,而且也不够优雅。
(2)第二种实现就是枚举每个物品选择的次数,要保证前i个物品在剩余 j 空间内具有最大价值,必然让每个物品选择0到W/w[i]次(W:背包空间,w[i]每个物品的体积),只要枚举物品的选择次数,并从中选择使得价值最大的次数k即可获得最大价值。
重点讲解第2种:
状态:
状态1:当前面对的物品类型 i
状态2:当前剩余的空间大小 j
dp [ i ][ j ]:面对物品 i,且空间剩余 j 时最大价值
方程:
面对第 i 种物品,可以有两种决策:
选择
选择物品 i 时,要考虑选择多少次,因此需要做一个次数的枚举,因为必然存在某个次数k使得背包价值最大,同时这个选择是在 第i - 1个物品剩余空间为 j 的基础上做出的有:dp [ i ] [ j ] = max(dp [ i - 1 ] [ j - k * w[i] ] +k * c[ i ] , ( 0 < k ≤ W / w [ i ] ) ) (0 <k \le W/w[i])) (0<k≤W/w[i]))
不选择
由于不选择第 i 个物品,说明第 i 个物品不能贡献最大值,因此直接取第i-1个物品的最大结果即可dp [ i ] [ j ] = dp [ i -1 ] [ j ]
总结
两者取大者即可dp[ i ] [ j ] = max(dp[ i - 1 ] [ j ],dp[ i -1 ][ j - k * w[i]]+k * c[i])
初值
初值为0
#include <bits/stdc++.h>
#define maxlen 205
using namespace std;
int w[maxlen];
int c[maxlen];
int dp[35][205];
int main() {
int m,n;
cin>>m>>n;
for(int i =1 ;i<=n; i++)
cin>>w[i]>>c[i];
//0.枚举每个物品
for(int i = 1 ;i<=n ;i++){
for(int j = 1 ;j<=m ;j++){
//1.不选择第i个物品
dp[i][j] = dp[i-1][j];
//2.选择,且尝试枚举数量
for(int k = 0 ;k<=j/w[i]; k++){
if(j>=k*w[i])
dp[i][j] = max(dp[i][j],dp[i-1][j-k*w[i]]+k*c[i]);
}
}
}
cout<<dp[n][m]<<endl;
return 0;
}
2.改进
显然,上述代码的时间复杂度为
O
(
n
m
∗
∑
i
=
1
n
W
/
w
[
i
]
)
O(nm*\sum_{i=1}^{n}W/w[i])
O(nm∗∑i=1nW/w[i]),达到
O
(
n
3
)
O(n^{3})
O(n3)级别,不是一个非常好的算法。仔细分析可以发现,内层枚举数量的循环主要是在选择的情况下产生的,因此重点在于简化选择情况下的过程。
改进主要方式为简单的数学推导,如下:
由状态转移方程可知:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
c
[
i
]
,
d
p
[
i
−
1
]
[
j
−
2
∗
w
[
i
]
]
+
2
∗
c
[
i
]
,
.
.
.
,
d
p
[
i
−
1
]
[
j
−
k
∗
w
[
i
]
]
+
k
∗
c
[
i
]
)
,
(
0
<
k
≤
W
/
w
[
i
]
)
dp [ i ] [ j ] = max( dp[ i - 1 ] [ j ],dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] +k*c[ i ]),(0< k \le W/w[i])
dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+c[i],dp[i−1][j−2∗w[i]]+2∗c[i],...,dp[i−1][j−k∗w[i]]+k∗c[i]),(0<k≤W/w[i]) ①
左右两边 对空间缩小w[ i ] 并加上 c[ i ] 有:
d
p
[
i
]
[
j
−
w
[
i
]
]
+
c
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
c
[
i
]
,
d
p
[
i
−
1
]
[
j
−
2
∗
w
[
i
]
]
+
2
∗
c
[
i
]
,
.
.
.
,
d
p
[
i
−
1
]
[
j
−
(
k
+
1
)
∗
w
[
i
]
]
+
(
k
+
1
)
∗
c
[
i
]
)
,
(
0
<
k
≤
W
/
w
[
i
]
)
dp [ i ] [ j - w[ i ] ] +c[ i ] = max( dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - (k+1)*w[i] ] + (k+1)*c[ i ]),(0< k \le W/w[i])
dp[i][j−w[i]]+c[i]=max(dp[i−1][j−w[i]]+c[i],dp[i−1][j−2∗w[i]]+2∗c[i],...,dp[i−1][j−(k+1)∗w[i]]+(k+1)∗c[i]),(0<k≤W/w[i]) ②
由于k+1项不符合题意(每个物品最多W/w[i]项就可以填满背包),直接舍弃即可
d
p
[
i
]
[
j
−
w
[
i
]
]
+
c
[
i
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
c
[
i
]
,
d
p
[
i
−
1
]
[
j
−
2
∗
w
[
i
]
]
+
2
∗
c
[
i
]
,
.
.
.
,
d
p
[
i
−
1
]
[
j
−
k
∗
w
[
i
]
]
+
k
∗
c
[
i
]
)
,
(
0
<
k
≤
W
/
w
[
i
]
)
dp [ i ] [ j - w[ i ] ] +c[ i ]= max( dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] + k*c[ i ]),(0<k \le W/w[i])
dp[i][j−w[i]]+c[i]=max(dp[i−1][j−w[i]]+c[i],dp[i−1][j−2∗w[i]]+2∗c[i],...,dp[i−1][j−k∗w[i]]+k∗c[i]),(0<k≤W/w[i]) ③
由上述在选择的情况下可知:
必然从选择1次到选择W/w[i]次之间中产生一个最大值:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
c
[
i
]
,
d
p
[
i
−
1
]
[
j
−
2
∗
w
[
i
]
]
+
2
∗
c
[
i
]
,
.
.
.
,
d
p
[
i
−
1
]
[
j
−
k
∗
w
[
i
]
]
+
k
∗
c
[
i
]
)
,
(
0
<
k
≤
W
/
w
[
i
]
)
dp [ i ] [ j ] = max( dp[ i - 1 ] [ j ],dp[ i - 1 ] [ j - w[ i ] ] + c[ i ], dp[ i - 1 ] [ j -2*w[ i ] ]+2*c[ i ] ,..., dp[ i -1 ] [ j - k*w[i] ] +k*c[ i ]),(0< k \le W/w[i])
dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+c[i],dp[i−1][j−2∗w[i]]+2∗c[i],...,dp[i−1][j−k∗w[i]]+k∗c[i]),(0<k≤W/w[i]) ④
显然①④右侧相等,所以左侧必然相等有:
dp[ i ][ j ] = dp[ i ][ j -w[ i ] ]+c[ i ]
在选择的情况下,这个式子实际上使用dp[ i ][ j -w[ i ]]代替了max(dp[i-1][ j - k * w[i])+k * c[ i ]) 这个需要循环找第i-1行最大值的式子,使用本行即第 i 行中已经计算出的式子来更新dp[ i ] [ j ] ,这个替换具有重要意义,直接去掉了一层循环。
#include <bits/stdc++.h>
#define maxlen 205
using namespace std;
int w[maxlen];
int c[maxlen];
int dp[35][205];
int main() {
int m,n;
cin>>m>>n;
for(int i =1 ;i<=n; i++)
cin>>w[i]>>c[i];
//0.枚举每个物品
for(int i = 1 ;i<=n ;i++){
for(int j = 1 ;j<=m ;j++){
//1.先默认不选择的情况
dp[i][j] = dp[i-1][j];
if(j>=w[i])
//2.使用本层的dp[i][j-w[i]]+c[i]替换了max(dp[i][j-k*w[i]]+k*c[i])
dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+c[i]);
}
}
cout<<dp[n][m]<<endl;
return 0;
}
3.简化
和01背包类似,实际上多重背包dp[ i ][ j ]的计算只使用到了本行的内容,因此只用保存一行的内容即可。容易得到:
#include <bits/stdc++.h>
#define maxlen int(1e7+1)
long dp[maxlen];
long v[maxlen];
long w[maxlen];
using namespace std;
int main() {
int n,W;
cin>>W>>n;
for(int i = 1 ;i<=n ;i++)
cin>>w[i]>>v[i];
for(int i = 1 ;i<=n ;i++){
for(int j = 0 ;j<=W ;j++){
if(j>=w[i]){
dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
cout<<dp[W]<<endl;
return 0;
}