背包問題


背包問題

一、01背包問題

【問題】:

N件物品和一個容量為V的背包。第i件物品的費用(即體積,下同)是w[i],價值是c[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。 基本思路:

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

用子問題定義狀態:即f[i][v]表示前i件物品(部分或全部)恰放入一個容量為v的背包可以獲得的最大價值。則其狀態轉移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+c[i]}

這個方程非常重要,基本上所有跟背包相關的問題的方程都是由它衍生出來的。所以有必要將它詳細解釋一下:“將前i件物品放入容量為v的背包中”這個子問題,若只考慮第i件物品的策略(放或不放),那么就可以轉化為一個只牽扯前i-1件物品的問題。如果不放第i件物品,那么問題就轉化為“前i-1件物品放入容量為v的背包中”;如果放第i件物品,那么問題就轉化為“前i-1件物品放入剩下的容量為v-w[i]的背包中”,此時能獲得的最大價值就是f [i-1][v-w[i]]再加上通過放入第i件物品獲得的價值c[i]

注意f[i][v]有意義當且僅當存在一個前i件物品的子集,其費用總和為v。所以按照這個方程遞推完畢后,最終的答案並不一定是f[N][V],而是f[N][0..V]的最大值。如果將狀態的定義中的“恰”字去掉,在轉移方程中就要再加入一項f[i-1][v],這樣就可以保證f[N][V]就是最后的答案。但是若將所有f[i][j]的初始值都賦為0,你會發現f[n][v]也會是最后的答案。為什么呢?因為這樣你默認了最開始f[i][j]是有意義的,只是價值為0,就看作是無物品放的背包價值都為0,所以對最終價值無影響,這樣初始化后的狀態表示就可以把“恰”字去掉。

優化空間復雜度 

以上方法的時間和空間復雜度均為O(N*V),其中時間復雜度基本已經不能再優化了,但空間復雜度卻可以優化到O(V)

先考慮上面講的基本思路如何實現,肯定是有一個主循環i=1..N,每次算出來二維數組f[i][0..V]的所有值。那么,如果只用一個數組f [0..V],能不能保證第i次循環結束后f[v]中表示的就是我們定義的狀態f[i][v]呢?f[i][v]是由f[i-1][v]f[i-1][v-w[i]]兩個子問題遞推而來,能否保證在推f[i][v]時(也即在第i次主循環中推f[v]時)能夠得到f[i-1][v]f[i-1][v-w[i]]的值呢?事實上,這要求在每次主循環中我們以v=V..0的逆序推f[v],這樣才能保證推f[v]f[v-w[i]]保存的是狀態f[i-1][v-w[i]]的值。

【偽代碼】:

  for i=1..N

   for v=V..0

     f[v]=max{f[v],f[v-w[i]]+c[i]};

其中f[v]=max{f[v],f[v-w[i]]+c[i]}相當於轉移方程f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+c[i]},因為現在的f[v-w[i]]就相當於原來的f[i-1][v-w[i]]。如果將v的循環順序從上面的逆序改成順序的話,那么則成了f[i][v]f[i][v-w[i]]推知,與本題意不符,但它卻是另一個重要的完全背包問題最簡捷的解決方案,故學習只用一維數組解01背包問題是十分必要的。

 

二、完全背包問題

【問題】:

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

【基本思路】:

這個問題非常類似於01背包問題,所不同的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取0件、取1件、取2件……等很多種。如果仍然按照解01背包時的思路,令f[i][v]表示前i種物品恰放入一個容量為v的背包的最大權值。仍然可以按照每種物品不同的策略寫出狀態轉移方程,像這樣:f[i][v]=max{f[i-1][v-k*w[i]]+k*c[i]|0<=k*w[i]<= v}

01背包問題的基本思路加以改進,得到了這樣一個清晰的方法。這說明01背包問題的方程的確是很重要,可以推及其它類型的背包問題。

  這個算法使用一維數組

【偽代碼】:

  for i=1..N

   for v=0..V

     f[v]=max{f[v],f[v-w[i]]+c[i]};

你會發現,這個偽代碼與01背包問題的偽代碼只有v的循環次序不同而已。為什么這樣一改就可行呢?首先想想為什么01背包問題中要按照v=V..0的逆序來循環。這是因為要保證第i次循環中的狀態f[i][v]是由狀態f[i-1][v-w[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][v-w[i]]。而現在完全背包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][v-w[i]],所以就可以並且必須采用v= 0..V的順序循環。這就是這個簡單的程序為何成立的道理。

這個算法也可以以另外的思路得出。例如,基本思路中的狀態轉移方程可以等價地變形成這種形式:f[i][v]=max{f[i-1][v],f[i][v-w[i]]+c[i]},將這個方程用一維數組實現,便得到了上面的偽代碼。

一個簡單有效的優化 

完全背包問題有一個很簡單有效的優化,是這樣的:若兩件物品ij滿足w[i]<=w[j]c[i]>=c[j],則將物品j去掉,不用考慮。這個優化的正確性顯然:任何情況下都可將價值小費用高的j換成物美價廉的i,得到至少不會更差的方案。對於隨機生成的數據,這個方法往往會大大減少物品的件數,從而加快速度。然而這個並不能改善最壞情況的復雜度,因為有可能特別設計的數據可以一件物品也去不掉。

轉化為01背包問題求解

既然01背包問題是最基本的背包問題,那么我們可以考慮把完全背包問題轉化為01背包問題來解。最簡單的想法是,考慮到第i種物品最多選V/w[i]件,於是可以把第i種物品轉化為V/w[i]件費用及價值均不變的物品,然后求解這個01背包問題。這樣完全沒有改進基本思路的時間復雜度,但這畢竟給了我們將完全背包問題轉化為01背包問題的思路:將一種物品拆成多件物品。

更高效的轉化方法是:把第i種物品拆成費用為w[i]*2^k、價值為c[i]*2^k的若干件物品,其中k滿足w[i]*2^k<V。這是二進制的思想,因為不管最優策略選幾件第i種物品,總可以表示成若干個2^k件物品的和。這樣把每種物品拆成O(log(V/w[i])+1)件物品,是一個很大的改進。

 

【總結】

完全背包問題也是一個相當基礎的背包問題,它有兩個狀態轉移方程,分別在“基本思路”以及“O(VN)的算法“的小節中給出。希望你能夠對這兩個狀態轉移方程都仔細地體會,不僅記住,也要弄明白它們是怎么得出來的,最好能夠自己想一種得到這些方程的方法。事實上,對每一道動態規划題目都思考其方程的意義以及如何得來,是加深對動態規划的理解、提高動態規划功力的好方法。

 

 

 

三、多重背包問題

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

【基本算法】:

這題目和完全背包問題很類似。基本的方程只需將完全背包問題的方程略微一改即可,因為對於第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。令f[i][v]表示前i種物品恰放入一個容量為v的背包的最大權值,則:f[i][v]=max{f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i]}。復雜度是O(V*n[i])

轉化為01背包問題

另一種好想好寫的基本方法是轉化為01背包求解:把第i種物品換成n[i]01背包中的物品,則得到了物品數為∑n[i]01背包問題,直接求解,復雜度仍然是O(V*n[i])

但是我們期望將它轉化為01背包問題之后能夠像完全背包一樣降低復雜度。仍然考慮二進制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換以后的物品。另外,取超過n[i]件的策略必不能出現。

方法是:將第i種物品分成若干件物品,其中每件物品有一個系數,這件物品的費用和價值均是原來的費用和價值乘以這個系數。使這些系數分別為 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數(注意:這些系數已經可以組合出1~n[i]內的所有數字)。例如,如果n[i]13,就將這種物品分成系數分別為1,2,4,6的四件物品。

分成的這幾件物品的系數和為n[i],表明不可能取多於n[i]件的第i種物品。另外這種方法也能保證對於0..n[i]間的每一個整數,均可以用若干個系數的和表示,這個證明可以分0..2^k-12^k..n[i]兩段來分別討論得出,並不難,希望你自己思考嘗試一下。

這樣就將第i種物品分成了O(logn[i])種物品,將原問題轉化為了復雜度為O(V*logn[i])01背包問題,是很大的改進。

 

 

四、混合三種背包問題

【問題】

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

 

01背包與完全背包的混合

  考慮到在01背包和完全背包中最后給出的偽代碼只有一處不同,故如果只有兩類物品:一類物品只能取一次,另一類物品可以取無限次,那么只需在對每個物品應用轉移方程時,根據物品的類別選用順序或逆序的循環即可,復雜度是O(VN)

【偽代碼】:

for i=1..N

 if i件物品是01背包

  for v=V..0

   f[v]=max{f[v],f[v-w[i]]+c[i]};

 else if i件物品是完全背包

  for v=0..V

   f[v]=max{f[v],f[v-w[i]]+c[i]};

 

再加上多重背包

  如果再加上有的物品最多可以取有限次,那么原則上也可以給出O(VN)的解法:遇到多重背包類型的物品用單調隊列解即可。但如果不考慮超過NOIP范圍的算法的話,用多重背包中將每個這類物品分成O(log n[i])01背包的物品的方法也已經很優了。

 

五、二維費用的背包問題

【問題】

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

【算法】

  費用加了一維,只需狀態也加一維即可。設f[i][v][u]表示前i件物品付出兩種代價分別為vu時可獲得的最大價值。

  狀態轉移方程就是:f [i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+c[i]}。如前述方法,可以只使用二維的數組:當每件物品只可以取一次時變量vu采用逆序的循環,當物品有如完全背包問題時采用順序的循環。當物品有如多重背包問題時拆分物品。

物品總個數的限制

  有時,“二維費用”的條件是以這樣一種隱含的方式給出的:最多只能取M件物品。這事實上相當於每件物品多了一種“件數”的費用,每個物品的件數費用均為1,可以付出的最大件數費用為M。換句話說,設f[v][m]表示付出費用v、最多選m件時可得到的最大價值,則根據物品的類型(01、完全、多重)用不同的方法循環更新,最后在f[0..V][0..M]范圍內尋找答案。

另外,如果要求“恰取M件物品”,則在f[0..V][M]范圍內尋找答案。

 

六、分組的背包問題

【問題】

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

【算法】

這個問題變成了每組物品有若干種策略:是選擇本組的某一件,還是一件都不選。也就是說設f[k][v]表示前k組物品花費費用v能取得的最大權值,則有f[k][v]=max{f[k-1][v]f[k-1][v-w[i]]+c[i]|物品i屬於第k}

【偽代碼】:

for 所有的組k

    for v=V..0

        for 所有的i屬於組k

      f[v]=max{f[v],f[v-w[i]]+c[i]}

  注意這里的三層循環的順序,for v=V..0”這一層循環必須在“for 所有的i屬於組k”之外。這樣才能保證每一組內的物品最多只有一個會被添加到背包中。

另外,顯然可以對每組中的物品應用完全背包中“一個簡單有效的優化”。

 

 

總結】

 

1、 最長不降子序列(LIS)

 

f[i]=max{f[j]}+1 (a[j]<=a[i],j<i) 可以用樹狀數組優化到 nlogn

 

2、 最長公共子序列(LCS)

 

If (a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;

 

Else f[i][j]=max(f[i-1][j],f[i][j-1]);

 

如果 AB 兩串的元素個數相同且各個元素也相同(比如 AB 均由 1~n 的數字組成)則可以將排序然后計算的最長上升子序列。

 

3、 區間 DP

 

枚舉起始點,枚舉區間長度,再枚舉區間分界點。復雜度 n³。

 

如果是個環(比如環形的石子合並)則把環變成長度為 2n-1 鏈,然后 DP。只是枚舉區間長度最多是 n,然后取最大值即可。

 

4、 坐標 DP

 

其實這種東西我小學就知道了… f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];遞推即可。

 

5、 01 背包

 

n 個物品,取或不取(不可拆分),求最大價值。

 

f[i]=max{f[i],f[i-w[j]]+v[j]}

 

注意:枚舉 i 是要從 V  w[j]遞減循環

 

6、 完全背包

 

n 個物品,物品數量無限,求最大價值。

 

朴素: 01 背包的很像,只是多加了枚舉物品的數量:

 

f[i]=max{f[i-k*w[j]]+k*v[j]} (i-k*w[j]>0)

 

優化:省去了枚舉物品的數量,每次計算 i-k*w[j]時就已經包含了 i-(k-1)*w[j] 的情況

f[i]=max{f[i-w[j]]+v[j]}(w[j]<=i<=V)

 

注意:枚舉 i 是從 w[j] V 遞增循環

 

 

7、 多重背包

 

n 個背包,物品數量為 Mi,求最大價值。

 

朴素:f[i]=max{f[i-k*w[j]]+k*v[j]} 0<=k<=Mi)

 

優化:將物品的系數由自然數改成 1,2,2^2,…,2^k,Mj-2^k+1,,然后再做 01 背包。

 

 

8、 混合背包

 

在選取物品的時候特判一下即可。

 

for (int i=1;i<=n;i++)

 

if 第 i 件物品是 01 背包 then 01pack(Wi,Vi);

 

else if 第 i 件物品是完全背包 then Completepack(Wi,Vi); else if 第 i 件物品是多重背包 then Multiplepack(Wi,Vi,Ni);

 

 

9、 二維費用背包

 

只要能理解 01 背包就能很好的理解此類問題。

 

f[u][v]=max(f[u][v],f[u-Wi][v-Pi]+V[i]);

 

 

10 分組背包

 

n 個物品被分成 k 組,每組中的物品相互沖突,求最大價值。

 

大致有一些像 01 背包。

 

for (int p=1;p<=k;p++)

 

for (int j=v;j>=0;j--)

 

for i∈第 p 組的物品

 

f[j]=max(f[j],f[j-Wi]+Vi);

 

小優化:當 Wi>Wj && Vi<Vj 時可以直接刪掉 i 號物品

 

 

11 有依賴的背包

 

選這個物品就一定要選擇它的兒子物品。

 

按照拓撲序進行轉移。

 

12 背包求方案

 

在每次轉移時記錄 G[i,v]表示是否選了物品 i。

 

i=n;s=v;

 

While (i>0)

 

{

 

If G[i,j]==0 print 沒選 i

Else print 選了 i

 

s-=C[i];

 

i--;

 

}

 

字典序最小方案:倒着做背包,每次記錄,輸出即可。(具體請畫圖理解)

k 小方案:更新答案是進行歸並排序。

方案總數:把求 max 改成求 sum。

 

感謝各位與信奧一本通的鼎力相助!


免責聲明!

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



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