參考:
描述:
有N件物品和一個容量為V的背包。
第i件物品的體積是vi,價值是wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。
二維動態規划
f[i][j] 表示只看前i個物品,總體積是j的情況下,總價值最大是多少。 result = max(f[n][0~V]) f[i][j]:
不選第i個物品:f[i][j] = f[i-1][j];
選第i個物品:f[i][j] = f[i-1][j-v[i]] + w[i](v[i]是第i個物品的體積) 兩者之間取最大。 初始化:f[0][0] = 0 (啥都不選的情況,不管容量是多少,都是0?)
代碼如下:
n, v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) # 初始化,先全部賦值為0,這樣至少體積為0或者不選任何物品的時候是滿足要求 dp = [[0 for i in range(v+1)] for j in range(n+1)] for i in range(1, n+1): for j in range(1,v+1): dp[i][j] = dp[i-1][j] # 第i個物品不選 if j>=goods[i-1][0]:# 判斷背包容量是不是大於第i件物品的體積 # 在選和不選的情況中選出最大值 dp[i][j] = max(dp[i][j], dp[i-1][j-goods[i-1][0]]+goods[i-1][1]) print(dp[-1][-1])
一維動態優化
從上面二維的情況來看,f[i] 只與f[i-1]相關,因此只用使用一個一維數組[0~v]來存儲前一個狀態。那么如何來實現呢?
第一個問題:狀態轉移
假設dp數組存儲了上一個狀態,那么應該有:
dp[i] = max(dp[i] , dp[i-v[i]]+w[i])
max函數里面的dp[i]代表的是上一個狀態的值。
第二個問題:初始化
這里開始初始化一個長度為V+1一維數組,選取0個物品時,體積為0~V時的最大價值(值全部為0)。
第三個問題:遞推關系
試想一下,我要保證求取第i個狀態時,用到一維數組中的值是第i-1個狀態的。如果,我從前往后推,那么當我遍歷到后面時,我用到的狀態就是第i個狀態而不是第i-1個狀態。比如:
dp[i] = max(dp[i] , dp[i-v[i]]+w[i])
這里的dp[i-v[i]]是已經重新賦值的,而不是上一個狀態的值,所以這樣是錯誤的。因此,我們要從后往前推。
n, v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) dp = [0 for i in range(v+1)] for i in range(n): for j in range(v,-1,-1): # 從后往前 if j >= goods[i][0]: dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1]) print(dp[-1])
確定體積的情況
如果我要求的不是盡可能最大的價值,而是剛好等於背包容量的最大價值,那么該如何去做呢?
完全背包問題
描述:
有N件物品和一個容量為V的背包,每件物品都有無限個!。
第i件物品的體積是vi,價值是wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。
一維動態規划
完全背包問題跟01背包問題最大的區別就是每一個物品可以選無數次,因此當我們考慮到第i個物品時,我們應該考慮的情況是:不選這個物品、選一次這個物品、選兩次這個物品......,直到不能再選(選的次數k,k*v[i] > j,j為當前背包容量),然后再從這些情況中選最大的。代碼如下:
n, v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) dp = [0 for i in range(v+1)] for i in range(n): for j in range(v,-1,-1): # 從后往前 k = j//goods[i][0] # 能選多少次 # 從這些次里面取最大 dp[j] = max([dp[j- x* goods[i][0]] + x * goods[i][1] for x in range(k+1)]) print(dp[-1])
一維動態規划(優化)
剛剛那個問題,我們是延續01背包的問題,從后往前遞推。但是對於這個問題,其實可以通過從前往后遞推。如何理解呢?
假設在考慮第i個物品時的兩個狀態:
A:dp[k*v[i] + x]
B:dp[(k-1)*v[i] + x]
根據前面的歸納,從前一個狀態遞推過來:
要求A的值,應該要從k+1個狀態中選出最大的:
dp[x] + k*w[i] dp[v[i] + x] + (k-1)*w[i] dp[2*v[i] + x] + (k-2)*w[i] ... ... dp[(k-1)*v[i] + x] + w[i] dp[k*v[i] + x
要求B的值,應該要從k個狀態中選出最大的:
dp[x] + (k-1)*w[i] dp[v[i] + x] + (k-2)*w[i] dp[2*v[i] + x] + (k-3)*w[i] ... ... dp[(k-2)*v[i] + x] + w[i] dp[(k-1)*v[i] + x
我們可以看到,一一對應過來的話,這兩個狀態實際上只差一個w[i]的值。因此:
一方面我們可以根據前一個狀態(i-1)推出此時的狀態,另一方面由於當前狀態前面的值也是當前問題的子問題,因此我們也可以從前面的值推到后面的值。
從前往后遞推的代碼如下:
n, v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) dp = [0 for i in range(v+1)] for i in range(n): for j in range(v+1): if j >= goods[i][0]: dp[j] = max(dp[j], dp[j-goods[i][0]] + goods[i][1]) print(dp[-1])
多重背包問題
描述:
有N件物品和一個容量為V的背包。
第i件物品的體積是vi,價值是wi,數量是si。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包流量,且總價值最大。
一維動態規划
其實跟上面的完全背包問題類似,只不過我們從后往前遞推的時候,物體i選取的次數應該要重新考慮下:
k = min(s[i], j//v[i]), j為當前的背包容量
代碼如下:
n,v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) dp = [0 for i in range(v+1)] for i in range(n): for j in range(v, -1, -1): # 考慮兩種情況的最小值 k = min(j//goods[i][0], goods[i][2]) dp[j] = max([dp[j-x*goods[i][0]] + x*goods[i][1] for x in range(k+1)]) print(dp[-1])
一維動態規划(轉換01背包)
想法很簡單,直接把背包中的物品展開,展成很多數量為1的物品,這樣就轉換為01背包問題。代碼如下:
n,v = map(int, input().split()) goods = [] for i in range(n): goods.append([int(i) for i in input().split()]) new_goods = [] # 展開 for i in range(n): for j in range(goods[i][2]): new_goods.append(goods[i][0:2]) goods = new_goods n = len(goods) # 01背包問題 dp = [0 for i in range(v+1)] for i in range(n): for j in range(v,-1,-1): if j>= goods[i][0]: dp[j] = max(dp[j], dp[j - goods[i][0]] + goods[i][1]) print(dp[-1])