完全背包问题
提出问题:
现在有
N
N
N个物品,和一个容量为
V
V
V的背包,每一种物品都有无限件可以使用。第
i
i
i种物品的费用是
C
i
C_{i}
Ci,价值是
W
i
W_{i}
Wi.求解将那些物品装入背包,在总重量不超过背包容量的情况下,使得背包内的物品价值最大。
求解问题:
对于每一个物品,我们有无限件,对于每一个物品,问题已经不是取或者是不取,而是选取0件、取1件、取2件直到取
⌊
V
/
C
i
⌋
\left\lfloor V/C_{i} \right\rfloor
⌊V/Ci⌋。
按照01背包的思路:建立动态规划表F[i,v]表示前i种物品放在一个容量为v的背包的最大价值。现在写出状态方程:
F
[
i
,
v
]
=
m
a
x
{
F
[
i
−
1
,
j
−
k
C
i
]
+
k
W
i
 
∣
 
0
≤
k
C
i
≤
v
}
F[i,v] = max\{F[i-1,j-kC_{i}] + kW_{i}\, | \, 0 \leq kC_{i} \leq v\}
F[i,v]=max{F[i−1,j−kCi]+kWi∣0≤kCi≤v}
def completePack(w,v,maxCap):
dp = [[0]*(maxCap+1) for _ in range(len(w)+1)]
for i in range(1,len(w)+1):
for j in range(1,maxCap+1):
if j < w[i-1]:
dp[i][j] = dp[i-1][j]
else:
maxnum = 0
for k in range((j//w[i-1])+1):
maxnum = max(maxnum,dp[i-1][j-k*w[i-1]] + k*v[i-1])
dp[i][j] = maxnum
return dp
w = [2,3,4,5]
v = [3,4,5,6]
V = completePack(w,v,10)
V
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 6, 6, 9, 9, 12, 12, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15]]
求解状态方法,需要有 O ( V N ) O(VN) O(VN)个状态求解,但是求解每一个状态的时间已经不是常数时间,求解F[i,v]的时间是 O ( V C i ) O(\frac{V}{C_{i}}) O(CiV),所以总的复杂度是 O ( N V ∑ V C i ) O(NV\sum\frac{V}{C_{i}}) O(NV∑CiV).
一个简单有效的优化:
- 现在有两件物品i,j满足 C i ≤ C j C_{i} \leq C_{j} Ci≤Cj且 W i ≥ C j W_{i} \geq C_{j} Wi≥Cj,就可以直接去掉物品j。
- 如果物品的重量大于背包容量可以直接去掉。
- 如果重量相同,我们取出价值最高的物品。
转换为01背包问题求解:把第i种物品换成
M
i
M_{i}
Mi件01背包的物品,
M
i
=
⌊
V
C
i
⌋
M_{i} = \left\lfloor \frac{V}{C_{i}} \right\rfloor
Mi=⌊CiV⌋.所以我们现在就有
∑
M
i
\sum M_{i}
∑Mi的物品。
现在建立动态规划的状态表。
F
[
i
]
[
j
]
:
F[i][j]:
F[i][j]:表示容量为j的背包装到前i个物品物品的最大价值。
那么问题F[i][j]:就会变成不装这个物品
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j],多装一个这个物品
d
p
[
i
]
[
j
−
w
i
]
dp[i][j-w_{i}]
dp[i][j−wi]。
我们写出递归式:
F
[
i
,
v
]
=
m
a
x
(
F
[
i
−
1
,
j
]
,
F
[
i
,
j
−
w
i
]
+
v
i
)
F[i,v] = max(F[i-1,j], F[i,j-w_{i}] + v_{i})
F[i,v]=max(F[i−1,j],F[i,j−wi]+vi)
def completePack(w,v,maxCap):
dp = [[0]*(maxCap+1) for _ in range(len(w)+1)]
for i in range(1,len(w)+1):
for j in range(1,maxCap+1):
if j < w[i-1]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-w[i-1]]+ v[i-1])
return dp
w = [2,3,4,5]
v = [3,4,5,6]
V = completePack(w,v,10)
V
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 6, 6, 9, 9, 12, 12, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15],
[0, 0, 3, 4, 6, 7, 9, 10, 12, 13, 15]]
def completePack(w,v,maxCap):
dp = [0]*(maxCap+1)
for i in range(1,len(v)+1):
for j in range(1,maxCap+1):
if j >= w[i-1]:
dp[j] = max(dp[j], dp[j-w[i-1]] + v[i-1])
return dp
def allComplete(dp,w,v,maxCap):
for i in range(1,len(v)+1):
completePack(dp,w[i-1],v[i-1],maxCap)
def completePack(dp,w,v,maxCap):
for j in range(1,maxCap+1):
if j >= w:
dp[j] = max(dp[j], dp[j- w] + v)
w = [2,3,4,5]
v = [3,4,5,6]
maxCap = 8
dp = [0]*(maxCap+1)
allComplete(dp, w,v,maxCap)
dp
[0, 0, 3, 4, 6, 7, 9, 10, 12]
3.多重背包问题
提出问题:
有N种物品和一个容量为V的背包。第i种物品最多有
M
i
M_{i}
Mi件可用,每件耗费的空间是
C
i
C_{i}
Ci,价值是
W
i
W_{i}
Wi。求解怎么放物品使得背包装的物品价值最大.
基本算法:
对于第i种物品有
M
i
+
1
M_{i}+1
Mi+1种策略:取0件、取1件、…、取
M
i
+
1
M_{i}+1
Mi+1件。
F
[
i
,
v
]
F[i,v]
F[i,v]表示前i种物品放入一个容量为v的背包的最大价值。
F
[
i
,
j
]
=
m
a
x
(
F
[
i
−
1
,
j
−
k
∗
C
i
]
+
k
∗
W
i
 
∣
 
0
≤
k
≤
M
i
)
F[i,j] = max(F[i-1,j - k * C_{i}] + k * W_{i}\, |\, 0 \leq k \leq M_{i} )
F[i,j]=max(F[i−1,j−k∗Ci]+k∗Wi∣0≤k≤Mi)
def mutiplePack(dp, w , v , m, maxCap):
for i in range(1,len(w)+1):
for j in range(1,maxCap+1):
if j < w[i-1]:
dp[i][j] = dp[i-1][j]
else:
maxnum = 0
for k in range(m[i-1]+1):
if k * w[i-1] <= j:
maxnum = max(maxnum, dp[i-1][j-k*w[i-1]] + k*v[i-1])
dp[i][j] = maxnum
w = [2,3,4,5]
v = [3,4,5,6]
m = [3,3,2,2]
maxCap = 8
dp = [[0]*(maxCap+1) for _ in range(len(w)+1)]
mutiplePack(dp,w,v,m,maxCap)
dp
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 3, 6, 6, 9, 9, 9],
[0, 0, 3, 4, 6, 7, 9, 10, 11],
[0, 0, 3, 4, 6, 7, 9, 10, 11],
[0, 0, 3, 4, 6, 7, 9, 10, 11]]
转换为01背包问题
def zeroOnePack(dp,w,v,maxCap):
for j in range(maxCap,0,-1):
if j >= w:
dp[j] = max(dp[j],dp[j-w]+v)
def allZeroOne(dp,w,v,maxCap):
for i in range(len(w)+1):
zeroOnePack(dp,w[i-1],v[i-1],maxCap)
w = [2,3,4,5]
v = [3,4,5,6]
maxCap = 8
dp = [0]*(maxCap+1)
allZeroOne(dp,w,v,maxCap)
dp
[0, 0, 3, 4, 5, 7, 8, 9, 10]
def allMutiplePack(dp,w,v,m,maxCap):
for i in range(len(w)+1):
multiplePack(dp,w[i-1],v[i-1],m[i-1], maxCap)
def multiplePack(dp, w, v, m, maxCap):
if w * m >= maxCap:
completePack(dp,w,v, maxCap)
k = 1
while k < m:
zeroOnePack(dp,k*w,k*v, maxCap)
m = m - k
k = 2*k
zeroOnePack(dp, w * m, v * m ,maxCap)
w = [2,3,4,5]
v = [3,4,5,6]
m = [2,2,3,3]
maxCap = 8
dp = [0]*(maxCap+1)
allMutiplePack(dp,w,v,m,maxCap)
dp
[0, 0, 3, 4, 6, 7, 8, 10, 11]