背包問題基本解法 —— 《背包九講》筆記


  相對於轉載文章,我更喜歡寫上一篇筆記,開篇給出原文鏈接。這樣,能有些自己的東西,總結一番,對知識的理解能加深一層;別人看來,也更有價值。

  今天做USACO題目時,一道題不會,網上查到解法是01背包,於是重新看了《背包九講》。相比第一次看,理解深的多,可見我還是在進步的,只要我沒停下腳步。如果大家想看原文,那么只需要百度“背包九講”就好了,百度文庫中的“背包九講 2.0”是正版,作者是崔添翼前輩,網上好像稱他為dd大牛。這篇文章可以說是“背包問題”的權威了,如果我了解無誤的話,背包問題的整套解法就是這篇文章總結出來的。在此,感謝崔添翼前輩的無私奉獻!

  本篇筆記,暫時只對01背包、完全背包問題基本解法做出記錄,今后如果用到更多,或許會回來更新。

------------------------------------------------------------------------------------------------------------------------------------------------

  背包問題模型:容量為V的背包,有N件物品,編號為i的物品,所占容量為Ci,所創造價值為Wi。

  01背包問題:每件物品只有一件,可以選擇放或不放(即取0件或1件,故名01)。

  完全背包問題:每件物品有無窮件,只要裝得下放多少都行(物品所取件數,所有值都可能取到,故名完全)。

  背包問題設問:

    1、最多能創造多少價值?

    2、背包放滿時,最多(最少)能創造多少價值?

    3、背包放慢時,總共有多少種方案?

    ……

  這就是最基礎的背包問題了,下面一一講解。

------------------------------------------------------------------------------------------------------------------------------------------------

我們首先以01背包+問題1為例。

  對於背包問題,我們的基本思路是這樣的:我們一件一件去放物品,從第1件開始直到第N件。設d[i][v]為放完第i件物品時,所占容量為v的情況下最大價值是多少。

  由於我們的問題1並不要求放滿背包,那么顯然有邊界條件d[0][0~N]=0,這表示我們一件物品不放的時候,可以認為已經占了任意容量,創造的最大價值為0。

  而后,我們開始一件一件放物品,c++代碼如下:

for(int i=1;i<=N;++i)
    for(int v=Ci;v<=V;++v) d[v][i]=max(d[v][i-1],d[v-Ci][i-1]+Wi);

  應該很容易理解,d[v][i]只可以由兩種情況更新,一種是d[v][i-1],這表示第i件物品不放入背包(或者說放入0件);一種是d[v-Ci][i-1]+Wi,這表示第i件物品放入背包。我們用這兩種情況的最大值去更新d[v][i],那么便考慮了所有情況,從而得到了d[v][i]得到了滿足定義的值。

  這便是動態規划的思路了,很容易理解,也很好寫。那么我們來分析一下它的復雜度:時間復雜度O(NV),空間復雜度O(NV)。

  下面我們來考慮優化。這種算法是考慮了所有情況,從而保證了正確性,因此時間復雜度上基本是無法優化的;而空間上,可以看到我們是層層遞推上去的,求第i件物品的d[0~V][i]時,我們只需要用到d[0~V][i-1]而已,因此我們至少可以把空間縮減到兩層,也就是O(2V)。而實際上,本題是可以讓空間為一層的,即O(V)。

  這時,我們的初始化代碼是:

for(int v=0;v<=V;++v) d[v]=0;

  而遞推代碼則如下:

for(int i=1;i<=N;++i)
    for(int v=V;v>=Ci;--v) d[v]=max(d[v],d[v-Ci]+Wi);

  其實並不難理解,我們把內層循環的順序顛倒了。這樣一來,很容易發現,我們內層循環至v時,d[v+1~V]實際上都是d[v+1~V][i],而d[0~v]則是d[0~v][i-1]。因此,黨我們執行d[v]=max(d[v],d[v-Ci]+Wi)時,它實際上就相當於d[v][i]=max(d[v][i-1],d[v-Ci][i-1]+Wi),因為執行完這句,d[v]就相當於是d[v][i]了。

  如果上面的話你理解了,那么你也就明白了01背包問題的解法,時間O(NV)空間O(V)的解法。

那么我們討論下01背包+問題2

  問題2很容易,我們要把背包放滿,那么只需要修改一下初始化代碼:

d[0]=0;
for(int v=1;v<=V;++v) d[v]=-∞;

  很好理解,一件物品不放時,只有d[0]有意義,其余容量都不可達到。因此,我們設為-∞,保證后面不會被max選到。而后面,只需要使用同樣的遞推代碼,意義完全不變。這算法正確性太顯然,我就不說明了。

接下來,我們來討論完全背包問題

  完全背包中,每件物品可以放無窮件。如果我們考慮空間為O(NV)的算法,那么會相對復雜一點,這里不予討論;而實際上,我們也只需要O(V)的空間,代碼如下:

for(int i=1;i<=N;++i)
    for(int v=Ci;v<=V;++v) d[v]=max(d[v],d[v-Ci]+Wi);

  很簡單,只需要把內層循環恢復正序即可。我這里不加解釋,相信大家簡單想想便可以理解。

  而對於問題1和問題2,和01背包一樣,相對應修改初始化部分即可,代碼完全一樣。

那么,問題3呢?

  這個問題很容易,我也不打算給出代碼。有興趣的讀者可以自己想想,我在USACO上所做那道題便是這種類型,想不出來的同學可以參考一下,這是博文鏈接

------------------------------------------------------------------------------------------------------------------------------------------------

  至此,本篇對背包問題的基礎內容講解完畢。如果大家仍有興趣了解,不妨去看看《背包九講》原文。


免責聲明!

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



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