模板問題:Knapsack (LeetCode: Coin Change)[揹包問題九講筆記]

Tech In Pieces發表於2020-12-19

揹包問題可以說是DP經典中的經典了。
大名鼎鼎的揹包問題九講涵蓋了所有的揹包問題。
因為之前沒寫過相關文章 因此我們就在這裡講一下所有的揹包問題。
下面的講解將基於:
dd大牛的揹包九講-揹包問題彙總
有興趣的同學可以參照這個原版。

首先 揹包問題是啥我就不細講了 抽象出來,就是在有限的空間之內儘可能放最多或者總和最有價值的東西。
細分一些 可以分成以下幾個問題:

01揹包 – 每個物品要麼放要麼不放
完全揹包 – 每種物品有無限多個
多重揹包問題 – 每種物品有固定多個
混合揹包問題 – 混合了上面三種問題
二維費用:揹包除了除了體積之外 有另外的限制
分組的揹包問題
有依賴的揹包問題
泛化物品 --抽象出來問題然後應用到更廣泛的地方

然後我們分別來看這八種問題應該怎麼樣解決
最後我們看一下揹包問題都有哪些變形的問法。

1 01揹包問題

//f[i][v] represents that the max total value with only i items and max weight limit is v. so each time, we are gonna choose from we choose this ith item and we didn't choose it
f[i][v]=Math.max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

有個要注意的點 就是初始化:當可選數目為0的時候 要初始化為Integer.MAX_VALUE.
2 完全揹包問題
就是說 之前只有拿或者不拿兩種狀態 現在有k種無窮種狀態,也就是說現在我們要有三層for loop, 我們要把最內層變為(不拿當前的 只拿一個 拿兩個 拿三個…拿無窮個)
但是怎麼樣表示拿無窮個呢?事實上 拿無窮個是不可能的。我們只需要把有限的空間拿滿就可以了。也就是說 內層函式只需要這樣:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

3 多重揹包問題
題目描述:

有N種物品和一個容量為V的揹包。第i種物品最多有n[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

顯而易見 這個問題是上面兩個問題的混合 我們需要考慮被填滿的問題 只需要加一個if語句來判斷v-k*c[i]是不是大於等於0就行了

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

4 混合揹包問題
題目描述:

如果將P01、P02、P03混合起來。也就是說,有的物品只可以取一次(01揹包),有的物品可以取無限次(完全揹包),有的物品可以取的次數有一個上限(多重揹包)。應該怎麼求解呢?

就根據不同的物品型別用If分支就行了。
5 二維費用的揹包問題:
問題描述:

對於每件物品,具有兩種不同的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(揹包容量)。問怎樣選擇物品可以得到最大的價值。設這兩種代價分別為代價1和代價2,第i件物品所需的兩種代價分別為a[i]和b[i]。兩種代價可付出的最大值(兩種揹包容量)分別為V和U。物品的價值為w[i]。

這就相當於費用加了一維 其實本質上和01揹包沒有本質上的區別。
跟之前相比 狀態也就加一維就行。
設f[i][v][u]表示前i件物品付出兩種代價分別為v和u時可獲得的最大價值。

f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]} 

6 分組揹包問題
題目描述:

有N件物品和一個容量為V的揹包。第i件物品的費用是c[i],價值是w[i]。這些物品被劃分為若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

問題變得越來越雞賊了 簡化起來就是:有些物品之間互斥,選了這個就不能遠某些其他的了。
這個問題變成了每組物品有若干種策略:是選擇本組的某一件,還是一件都不選。也就是說設f[k][v]表示前k組物品花費費用v能取得的最大權值,則有:

f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i屬於第k組}

當然 其實這道題的本質和01揹包沒有什麼區別 只是01揹包有n個組(n就是所有物品的數量)而分組揹包就是有k組。
很明顯 我們要遍歷所有的組 遍歷所有的volumn 虛擬碼如下:
for i = 1 to K (K組放在最外面 這樣才能保證我們每次都只取一組)
for v = 0 to V:
for 所有的i都屬於組k
f[v] = Math.max(f[v], f[v - c[i]] + w[i]);
注意這裡的三層迴圈的順序,for v這一層迴圈必須在for 所有的組都屬於組K之外。這樣參能保證每一組內最多有一個被新增到揹包中。為什麼是這樣呢?就想像一個樹 一層一層的,我們沿著這三個for迴圈從上往下走 這樣每個路徑才會從每組裡面選擇而且 只選擇一個。
7 有依賴的揹包問題
問題描述:

這種揹包問題的物品間存在某種“依賴”的關係。也就是說,i依賴於j,表示若選物品i,則必須選物品j。為了簡化起見,我們先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。

這道題有點拓撲排序那味了,也就是說 我們給定圖集合 要求從這些圖中找出權重值和最大的那些節點。有些節點可以直接獲取 但是有些節點必須要通過固定節點才能獲得。
這聽起來很奇怪,但是這是一種樹形DP。

事實上,這是一種樹形DP,其特點是每個父節點都需要對它的各個兒子的屬性進行一次DP以求得自己的相關屬性。這已經觸及到了“泛化物品”的思想。看完P08後,你會發現這個“依賴關係樹”每一個子樹都等價於一件泛化物品,求某節點為根的子樹對應的泛化物品相當於求其所有兒子的對應的泛化物品之和。

8 泛化物品

現在我們想一下:上面的問題變來變去,有沒有一種更巨集觀的解釋?
是有的 就是採用泛化物品的思想。
一個揹包問題中,可能會給出很多條件,包括每種物品的費用、價值等屬性,物品之間的分組、依賴等關係等。但肯定能將問題對應於某個泛化物品。也就是說,給定了所有條件以後,就可以對每個非負整數v求得:若揹包容量為v,將物品裝入揹包可得到的最大價值是多少,這可以認為是定義在非負整數集上的一件泛化物品。這個泛化物品——或者說問題所對應的一個定義域為非負整數的函式——包含了關於問題本身的高度濃縮的資訊。一般而言,求得這個泛化物品的一個子域(例如0…V)的值之後,就可以根據這個函式的取值得到揹包問題的最終答案。
綜上所述,一般而言,求解揹包問題,即求解這個問題所對應的一個函式,即該問題的泛化物品。而求解某個泛化物品的一種方法就是將它表示為若干泛化物品的和然後求之。

揹包問題有哪些變形和其他問法?
一般的來說,我們就是需要求 給定固定容積容器,求能夠放進去的最大最小件數或者價值的物品。
變形問法:
輸出選的方案。這種的話就按照動態規劃輸出路徑的方式:錄下每個狀態的最優值是由狀態轉移方程的哪一項推出來的,換句話說,記錄下它是由哪一個策略推出來的。便可根據這條策略找到上一個狀態,從上一個狀態接著向前推即可。
具體如何做 請參見本博主博文 《動態規劃如何輸出路徑?》

相關文章