有 N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用 一次。
第 i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
解析:
状态表示:
f[i][j]
表示只看前 i
个物品,总体积是 j
的情况下的最大价值。状态转移:
不选第 i
个物品,f[i][j] = f[i-1]f[j]
,体积不变,与上一个物品的状态相等。
选第 i 个物品,f[i][j] = f[i-1][j-v[i]] + v[i]
,体积变小,加上当前物品的价值。
f[i][j] = max(1, 2)
边界:
f[0][j], f[i][0] = 0
,体积或物品为 0 的情况下最大价值是 0def func(v, w, m): # v是体积,w是价值,m是总体积
f = [[0]*(m+1) for _ in range(n+1)] # f[i][j]表示前i个物品,总体积是j的情况下的最大价值
for i in range(1, len(w) + 1): # 从第一个物品开始
for j in range(1, m + 1): # 体积从1开始
f[i][j] = f[i - 1][j] # 不选这个物品
if j >= v[i - 1]: # 若可以装下这个物品
f[i][j] = max(f[i-1][j], f[i-1][j-v[i-1]] + w[i-1]) # 选这个物品
print(f[n][m])
# print(max([max(x) for x in f]))
O(n*V)
O(n*V)
存储空间优化
可以将所有的状态用一维数组表示,空间复杂度降为 O(V)
。
关键点在于当前使用的状态一定要是上一层的状态。即还未被修改为这一层的状态。
因为当前状态之和上一层的状态有关,所以可以优化为一维数组。首先看两个状态转移:
f[i][j] = f[i-1][j]
f[i][j] = f[i-1][j-v[i]] + w[i]
第一个状态:我们可以直接用 f[j]
代替。因为遍历到 f[j]
时,f[j]
还没有改变,依旧是上一层的状态。所以,我们不用再去判断何时能选,何时不能选。可以统一。
第二个状态:当遍历到状态f[j]
时,因为j-v[j] < j
,所以状态f[j-v[j]]
已经遍历过了,是这一层的状态,我们想要的是上一层的f[j-v[j]]
。所以体积可以从大到小遍历,这样遍历到f[j]
时,f[j-v[j]]
还没有遍历到,依旧为上一层的状态。
def func(v, w, m): # m为总重量
f = [0]*(m+1)
for i in range(1, len(w)+1): # 物品[1, n]
for j in range(m, v[i-1]-1, -1): # 体积[m, v[i]]
f[j] = max(f[j], f[j - v[i-1]] + w[i-1])
print(f[m])
关于结果的优化表示
对于最后的结果可以直接输出 f[V],而不用遍历整个数组取最大值,是因为当我们 f[V] 全都初始化为0时, f[V] 表示体积小于等于 V 时可以获得的最大价值。
当我们只把 f[0] 初始化 0,其余的 f[i] 初始化 -INF,就能求出恰好体积为 V 时获得的最大价值。这样可以确保 f[V] 是从 f[0] 转移过来,从其他状态转移过来就是负无穷。
直观理解,当我们要求的是恰好体积为 V 时获得的最大价值。考虑这种情况,我们遍历体积是从1开始遍历到V,可其中有些体积并装不下任何物品,此时它们的状态应该是不存在的,且不应该转移到后面的状态。所以我们应该初始化为 -INF,这样状态转移到后面也是 -INF。
有 N N N 种物品和一个容量是 V V V 的背包,每种物品都有无限件可用。第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
解析:
状态表示:
res = max(f[0 ... m])
,m 为总体积。扩展01背包问题,v是体积,w是价值,m是总体积
def func(v, w, m):
n = len(w) - 1
f = [0]*(m+1)
for i in range(1, n + 1): # 物品[1, n]
for j in range(m, v[i-1]-1, -1): # 体积 [m, 1]
for k in range(1, j//v[i-1] + 1): # 个数 [1, j//v[i]]
f[j] = max(f[j], f[j - k*v[i-1]] + k*w[i-1])
print(f[m])
优化,只需将01背包中的第二个循环反向即可!
def func(v, w, m):
f = [0]*(m+1)
for i in range(1,len(w) + 1): # 物品[1, n]
for j in range(v[i-1], m+1): # 体积 [v[i], m]
f[j] = max(f[j], f[j - v[i-1]] + w[i-1])
print(f[m])
我们想一下,我们当初为何要逆序循环?
因为f[i][j]
是由f[i-1][j-v]
推导的,我们逆序循环正好保证了每个物品只被运用一次,但是如果我们正序循环,说明我们的f[i][j]
是由f[i][j-v]
推导出的,每个物品可以被运用多次。但这正是我们在完全背包里面想要的。
有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
解析:
状态表示:
状态转移:
是01背包的扩展,01背包问题是每个物品只有选和不选两种情况。多重背包问题是可以不选,可以选一个,两个,直到 s i s_i si 个
f[j] = max(f[j], f[j-v[i]]+w[i], f[j-2*v[i]]+2*w[i])
01背包问题扩展
def func(v, w, m):
f = [0]*(m+1)
for i in range(1, len(w) + 1): # 物品[1, n]
for j in range(m, v[i-1]-1, -1): # 体积 [m, v[i-1]]
for k in range(1, s[i-1] + 1): # 个数 [1, s[i]]
if k*v[i-1] <= j: # 确保背包装的下
f[j] = max(f[j], f[j - k*v[i-1]] + k*w[i-1])
print(f[m])
时间复杂度 O(n^3)
优化方法一:二进制优化方法
给定任意一个数 s,最少可以把 s 分成多少个数,每个数选或不选,使得这些的可以组合为小于等于 s 的所有的数。
和用老鼠试毒药一个道理,可以分为 s 的二进制位的个数(log (s+1) 上取整)。选或不选分别代表对应二进制位上是否为 1。
7 = 111 拆为 1= 001, 2=010, 4=100,分别代表每个二进制为上为 1
0 = 都不选,1 = 选1,2=选2,3=选1和2,4=选4,5=选1和4,6=选2和4,7=选1和2和4.
**如果拆到最后剩余一部分值,则直接加入背包。**比如,13 = 1101, 则分解为 0001, 0010, 0100, 0110. 前三个数字可以组合成 7 以内任意一个数,每个数再加上0110 (= 6) 之后可以组合成任意一个大于等于 6 小于等于 13 的数,所以依然能组成任意小于等于 13 的数。
from collections import namedtuple
def func_1(v, w, s, m): # v体积,w价值,s次数,m总体积
Good = namedtuple('Good', ['v', 'w'])
goods = [] # 存储所有物品
f = [0] * (m + 1)
# 把多个次数的物品二进制拆分
for i in range(len(w)):
k = 1
while k <= s[i]:
s[i] -= k
goods.append(Good(v[i]*k, w[i]*k))
k *= 2
if s[i] > 0: goods.append(Good(v[i]*s[i], w[i]*s[i]))
# 此时当作01背包来做
for good in goods:
for j in range(m, good.v - 1, -1): # 从大到小
f[j] = max(f[j], f[j - good.v] + good.w)
print(f[m])
优化方法二:单调队列优化方法
有 N 种物品和一个容量是 V 的背包。物品一共有三类:
每种体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
解析:
from collections import namedtuple
def func(v, w, s, m): # v体积,w价值,s个数,m总体积
Good = namedtuple('Good', ['kind', 'v', 'w'])
f = [0]*(m+1)
goods = []
for i in range(len(w)):
if s[i] < 0: # 01背包
goods.append(Good(-1, v[i], w[i]))
elif s[i] == 0: # 完全背包
goods.append(Good(0, v[i], w[i]))
else: # 多重背包转01背包
k = 1
while k <= s[i]:
s[i] -= k
goods.append(Good(-1, k*v[i], k*w[i]))
k *= 2
if s[i] >0: goods.append(Good(-1, s[i]*v[i], s[i]*w[i]))
# 对不同类型的背包问题转移
for good in goods:
if good.kind == -1: # 01背包
for j in range(m, good.v-1, -1):
f[j] = max(f[j], f[j - good.v] + good.w)
else: # 完全背包
for j in range(good.v, m+1):
f[j] = max(f[j], f[j - good.v] + good.w)
print(f[m])
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。输出最大价值。
解析:
f[i][j]
表示体积为 i ,重量为 j 时的背包最大价值。
先枚举物品,再枚举体积,再枚举重量。因为是01背包,所以体积和重量从大到小枚举。
def func(v, w, m, V, M): #v体积,w价值,m重量,V总体积,M总重量
f = [[0]*(M+1) for _ in range(V+1)]
for i in range(1, len(w)+1):
for j in range(V, v[i-1]-1, -1):
for k in range(M, m[i-1]-1, -1):
f[j][k] = max(f[j][k], f[j-v[i-1]][k-m[i-1]] + w[i-1])
print(f[V][M])
有 N 组物品和一个容量是 V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 ii是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。
解析:
状态转移
for i in [1, n]: # 物品总数
for j in [m, v]: # 体积从大到小枚举
f[j] = max(f[j], f[j-v[0]]++w[0], f[j-v[1]]++w[1], ...)
最后一层的意思是这一组的物品,可以都不选,可以选第一个,可以选第二个,第一选第三个…,取最大。
def func(v, w, m): # v分组体积[[v1. v2], [v1]], w分组价值[[w1, w2], [w1]],m总体积
f = [0]*(m+1)
for i in range(len(w)): # 枚举每一个组
for j in range(m, -1, -1): # 体积 [m, 0]
for k in range(0, len(v[i])): # 个数
if j >= v[i][k]:
f[j] = max(f[j], f[j - v[i][k]] + w[i][k])
print(f[m])
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 10^9+7 的结果。
解析:
f[i]
表示体积恰好为 i 的情况下背包的最大价值。
g[i]
表示体积为 i 的情况的方案数。
g[i]
就等于其中一种的方案数g[i]
就等于两种的方案数之和f
的所有初始化除了f[0]
都要设为 -INF,我们要求恰好体积为 i 的情况的最大价值。不然小于等于体积i的最大价值,不太好统计方案数。def func(v, w, m): # m为总重量
f = [float('-inf')]*(m+1)
g = [0]*(m+1)
f[0] = 0
g[0] = 1 # 体积为0的方案数为1
for i in range(1, len(w)+1): # 物品[1, n]
for j in range(m, v[i-1]-1, -1): # 体积[m, v[i]]
t = max(f[j], f[j - v[i-1]] + w[i-1]) # 取出最大值
s = 0
if t == f[j]: s = (s+g[j])%mod # 如果最大值等于f[j],那么加上g[j]
if t == f[j - v[i-1]] + w[i-1]: s= (s + g[j - v[i-1]])%mod # 如果最大值等于f[j-v[i-1]] + w[i-1],那么加上g[[j-v[i-1]]
f[j], g[j] = t, s
maxw = max(f)
res = 0
for i in range(len(f)):
if f[i] == maxw:
res = (res + g[i])%mod
print(res)
物品之间有依赖。