背包問題(轉自背包九講+對應題目)


一 、01背包問題

 題目

  有N件物品和一個容量為V的背包。放入第i件物品耗費的空間是Ci,得到的價值是Wi。求解將哪些物品裝入背包可使價值總和最大。

 思路

  這是最基本的背包問題:每種物品僅有一件,可以選擇放或不放。

  定義狀態:F[i,v]表示前i件物品恰放入一個容量為v的背包可以獲得的最大價值。則其狀態轉移方程便是:F[i,v]=max{F[i-1,v],F[i-1,v-Ci]+Wi}

  這個方程很重要,這里來解釋一下:“將前i件物品放入容量為v的背包中”這個子問題,若只考慮第i件物品的策略(放還是不放),就可以轉化為一個只和前i-1個物品相關的問題。如果不放,則轉化為F[i,v];如果放,則為F[i-1,v-Ci]+Wi.

  偽代碼如下:

  F[0,0..V] = 0

  for i = 1 to N

    for v = Ci to V

      F[i,v] = max{F[i-1,v],F[i-1,v-Ci]+Wi}

  空間優化

  將空間復雜度降為O(V),即只用一維數組。首先第一層循環一定是有的,每次計算出F[i,0..V]的所有值。由於i是遞增變化的,那么我們用一維數組F[0..V],每次循環時都能使用上一次循環的值,也就是i-1的數組值。接下來有個問題,怎么保證F[v]一定是由上一次的F[v]和F[v-Ci]轉化過來呢?

  這要求我們在第二層循環時遵循從V到0的遞減順序計算F[v],這樣能保證計算F[v]時,F[v-Ci]保存的是上一次的值,即F[i-1,v-Ci]。可以這么理解,由狀態轉移方程我們知道,F[i,v]是由F[i-1,v]或F[i-1,v-Ci]轉化得到,現在只看第二維,每次計算新的F值時都是從第二維比較小的狀態過來的,如v和v-Ci,都是小於等於v的,那么只有讓第二層從大到小更新,才保證了F[i,v]是由F[i-1,v]或F[i-1,v-Ci]轉化得到;否則就成了F[i,v]由F[i,v-Ci]得到的,這是不符合最初的算式的。

  偽代碼:

  F[0,0..V] = 0

  for i = 1 to N

    for v = V to Ci

      F[v] = max{F[v],F[v-Ci]+Wi}

  總結

  這樣我們可以抽象出一個處理一件01背包的物品過程。

  ZeroOnePack(F,C,W)

    for v = V to C

      F[v] = max(F[v],F[v-C]+W)

 

  有了上述函數,01背包的偽代碼就可以這樣寫:

  F[0..V] = 0

  for i = 1 to N

    ZeroOnePack(F,Ci,Wi)

  初始化問題

  若問題要求剛好裝滿背包,初始化時,則除了F[0]=0,F[1..V]均為-inf(非法解)。

  如果不要求裝滿,則初始化時F[0..V]=0。

 

二、完全背包問題

  題目

  有N種物品和一個容量為V的背包,每種物品都有無限件。放入第i件物品耗費的空間是Ci,得到的價值是Wi。求解將哪些物品裝入背包可使價值總和最大。

  思路

  這個問題於01背包相似,唯一不同的就是這里的物品有無限件。也就是其取法的策略不再是取或不取兩種了,而是取0件、取1件······直至取INT(V/Ci)件等多種。

  但我們仍然可以采用解決01背包的思路來求解這個問題。

  列出轉移方程:F[i,v]=max{F[i-1,v-k*Ci]+k*Wi | 0<=k*Ci<=v}

  總復雜度為O( V*N*Σ(V/Ci) ),比較大。

  優化

  一個簡單有效的優化:若兩件物品i、j滿足Ci<=Cj且Wi>=Wj,則可以將物品j去掉,不用考慮。

  轉化為01背包問題

  把第i種物品拆分成費用為Ci*2^k、價值為Wi*2^k的若干件物品,其中k取滿Ci*2^k<=V的非負整數。這就是二進制的思想,因為不管最優策略選幾件,其件數總是能表示成若干的2^k件物品的和。這樣就把每種物品拆成INT(log(V/Ci))件物品了。

  O(V*N)的算法

  這個算法采用一維數值,先上偽代碼:

  F[0,0..V] = 0

  for i = 1 to N

    for v = Ci to V

      F[v] = max{F[v],F[v-Ci]+Wi}

  這個偽代碼與01背包的偽代碼只有v的循環次序不同而已。

  為什么這個算法要以這樣的次序呢?首先想想為什么01背包的中要按照v遞減的次序來循環。讓v遞減是為了保證第i次循環中的狀態F[i,v]是由狀態F[i-1,v-Ci]遞推而來的。換句話說,這正是為了保證每件物品之選一次,保證在考慮“選入第i件物品”這個策略時,依據的是一個絕無已經選入第i件物品的子結果F[i,v-Ci]。而現在完全背包的特點是每種物品可選無限件,所以在考慮“加選一件第i件物品”這種策略時,正需要一個可能已經選入第i種物品的子結果F[i,v-Ci],因此必須采用遞增的順序循環。

  把上述式子寫成二維形式:F[i,v]=max{F[i-1,v],F[i,v-Ci]+wi}

  總結  

  最后抽象出處理一件完全背包類物品的過程偽代碼:

  CompletePack(F,C,W)

  for v = C to V

     F[v] = max{F[v],F[v-C]+W}

 

三、多重背包問題

  題目

  有N種物品和一個容量為V的背包。第i種物品最多有Mi件,放入第i件物品耗費的空間是Ci,得到的價值是Wi。求解將哪些物品裝入背包可使價值總和最大。

  思路

  這題和完全背包很相似。只是每種物品最多能取的個數不同了。

  照貓畫虎,寫出狀態轉移方程:

       F[i,v] = max{F[i-1,v-k*Ci]+k*Wi | 0<=k<=Mi}

  復雜度是O(V*ΣMi)

  轉化為01背包問題

  仍然考慮二進制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0……Mi件——均能等價於取若干件代換以后的物品。另外,取超過Mi的策略不能出現。

  方法是:將第i種物品分成若干件01背包中的物品,其中每一件物品有一個系數。這件物品的費用和價值均是原來的費用和價值乘以這個系數。令這些系數分別為1,2,2^2...2^(k-1),Mi-2^k+1,且k是滿足Mi-2^k+1>0的最大整數。例如Mi=13,那么k=3,對應系數分別為1,2,4,6。

  分成的這幾件物品的系數和為Mi,表明不可能取多於Mi件的第i種物品。另外這種方法也能保證對於0...Mi間的每一個整數,均可以用若干個系數的和表示。這樣就把第i種物品分成了logMi種物品,將復雜度降到了O(V*ΣlogMi)。

  總結

  下面給出O(logM)時間處理一件多重背包中物品的過程:

  MultiplePack(F,C,W,M)

    if C*M >= V

      CompletePack(F,C,W)

      return;

    k = 1

    while k < M

      ZeroOnePack(k*C,k*W)

      M = M - k

      k = 2*k

    ZeroOnePack(M*C,M*W)

  可行性問題O(V*N)的算法

  當問題是“每種有若干件物品能否填滿給定容量的背包”,只需考慮填滿背包的可行性,不需要考慮每件物品的價值時,多重背包問題同樣有復雜度O(V*N)的算法。

  下面介紹一種方法:設F[i,j]表示“用了前i種物品填滿容量為j的背包后,最多還剩下幾個第i種物品可用”,如果F[i,j]=-1則說明這種狀態不可行,若可行應,滿足0<=F[i,j]<=Mi。

  遞推求F[i,j]的偽代碼如下:

  F[0,1...V] = -1

  F[0,0] = 0

  for i = 1 to N

    for j = 0 to V

      if F[i-1,j] >= 0

        F[i,j] = Mi

      else

        F[i,j] = -1

    for j = 0 to V-Ci

      if F[i,j] > 0

        F[i,j+Ci] = max{F[i,j+Ci,F[i,j]-1}  

  最終F[N,0...V]便是多重背包可行性問題的答案。

 

四、混合三種背包問題

  若有的物品只能取一次,有的物品可取無限次,有的物品可取Mi次。該怎么求解呢?

  最清晰的做法時使用上述定義的三個過程:

  for i = 1 to N

    if 第i件物品屬於01背包

      ZeroOnePack(F,Ci,Wi)

    else if 第i件物品屬於完全背包

      CompletePack(F,Ci,Wi)

    else if 第i件物品屬於多重背包

      MultiplePack(F,Ci,Wi,Mi)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM