背包問題


01背包:
每件物品都有它的價值和體積,你的背包有一定容量,如何能獲取最大價值?

第一行有2個整數分別表示容量和物品數(n)
接下來n行每兩個數個分別代表一個物體的體積和價值

很顯然,每種物品只能拿一件

當然你也可以不拿

如果拿(前提是有足夠空間),就相當於背包少了v[i]的體積,多了c[i]的價值,

不拿的話(沒有空間)就是上一個物品占用當時最大的體積的價值

一層循環枚舉每一個物品

  二層循環枚舉可用的最大體積(為什么這么說呢?,因為一個物體的體積如果是5,那么只剩2肯定裝不下)

    如果能裝下(c代表價值v代表枚舉體積)

      f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);

    否則

       f[i][v]=f[i-1][v];//只能跟前面一樣

注意,枚舉的v是指最大占用v體積的情況下獲得最大的價值

v一定要倒着枚舉(從最大體積開始到1結束)

因為01背包只能選一個,你在選擇v-w[i]時,f[i-1][v-w[i]]其中一定沒有選過i,應該很容易理解吧.

 

這時dalao們就發現問題了,若有5000件物品背包容量是100000,就爆空間了,這怎么辦呢?

我們可以想一個問題,如果用15的空間既可以獲得10的價值也可以獲得50的價值,那么你選哪個?

都選50的吧

那么獲得10價值的方案還有用嗎?

顯然沒有用了

所以我們只需要記錄每一個容積所能獲得的最大價值就好了,這樣就可以壓縮成為一維,從而舍棄第一維,只要容積那一維(只儲存最大的價值)

 一層循環枚舉每一個物品

  二層循環枚舉可用的最大體積

    如果能裝下

      f[v]=max(f[v],f[v-w[i]]+c[i]);

    否則//自己等於自己,當然沒必要寫這句

 

完全背包:
每件物品都有它的價值和體積但每件物品可以無限拿,你的背包有一定容量,如何能獲取最大價值?

第一行有2個整數分別表示容量和物品數(n)
接下來n行每兩個數個分別代表一個物體的體積和價值

很顯然,每種物品可以拿無數件

當然你也可以不拿

如果拿(前提是有足夠空間),就相當於背包少了v[i]的體積,多了c[i]的價值,

不拿的話(沒有空間)就是上一個物品占用當時最大的體積的價值

我們先講一種便於理解的方法,即:
加一重循環枚舉個數k

當然,依然不能超出背包限制

狀態轉移方程f[v] = max{ f[v],f[ j - k*v[i]]+k* c[i]};

這應該每位dalao和像我一樣的蒟蒻都能明白吧

 

然后明白了就可以上一種玄學一的點了

一層循環枚舉每一個物品

  二層循環枚舉可用的最大體積

    如果能裝下(c代表價值v代表枚舉體積)

      f[v]=max(f[v],f[v-w[i]]+c[i]);

注意,枚舉的v是指最大占用v體積的情況下獲得最大的價值

v一定要正着枚舉(從1開始到最大體積結束)

因為完全背包可以無限選,你在選擇v-w[i]時,他就有可能已經選過了.

到這里有些像我一樣的蒟蒻就不懂了

我舉一個生動形象的例子

背包容量為5

有1個物品,體積為2,價值為3(這么簡單的例子不是因為我懶)

從5開始呢?

f[5]=f[5-2]+3;

f[4]=f[4-2]+3;

f[3]=f[3-2]+3;

f[2]=f[2-2]+3;

1和0不行

可以看出,從5到2,關系分別是

由3推5

由2推4

由1推3

由0推2

從一開始推出來的5開始沒有被用過的再被利用

從1開始呢?

1和0不行

f[2]=f[2-2]+3;

f[3]=f[3-2]+3;

f[4]=f[4-2]+3;

f[5]=f[5-2]+3;

可以看出,從5到2,關系分別是

由0推2

由1推3

由2推4

由3推5

可以看出:

由0推出的2又在推4的時候用到了,這就實現了完全背包

 

大家應該可以發現我在寫01背包和完全背包時有很多相同之處

這是因為01背包是一種特殊的完全背包

好啦,一篇博客結束啦

 

emmm...怎么能結束了呢,我又來更新博客啦,

二維費用背包,

即有兩個限制,比如,一件物品既有體積又有質量,你不能超出2者其中一個,求最大價值

明白了01背包和完全背包之后,這個問題就簡單許多

假如一種限制也沒有呢?

代碼應該是

    for(k=1; k<=c; k++) {
        ans=max(a[k]+ans,ans);
    }

加一種限制呢?(即只有體積限制)

    for(k=1; k<=c; k++) {
        for(i=n; i>=v[k]; i--) {
              a[i] = max(a[i-v[k]]+jz[k],a[i][j]);
        }
    }

區別是什么?

加了一層循環,又加了一維的空間

由此推出

    for(k=1; k<=c; k++) {
        for(i=n; i>=v[k]; i--) {
            for(j=m; j>=w[k]; j--) {
                    a[i][j] = max(a[i-v[k]][j-w[k]]+jz[k],a[i][j]);
            }
        }
    }

合情合理又簡單.

 

多重背包問題

即一個物品可以拿指定的次數(這才是正常的日常問題)

還是先考慮簡單的情況

轉換01背包(硬轉換)

再加一層循環枚舉個數,不能超過背包容量

    for(k=1; k<=c; k++) {
        for(i=n; i>=v[k]; i--) {
            for(j=1; j<=c[i]; j++) {
                    a[i] = max(a[i-j*w[k]]+j*jz[k],a[i]);
            }
        }
    }

這樣就連我這樣的蒟蒻也明白吧

接下來就要上一種更厲害的方法了

3=1+2

這個式子每個人都會吧

轉換2進制 11=1+10

那若給你1,10,100,1000,10000.....

這些二進制數,是不是能湊出所有的數呢?

任何一個二進制數每一位要么0,要么1,所以都能湊出來

 

舉個栗子

假如二進制第3位是1那么就加上1000,如果是0就不加

好啦回歸正題

1,10,100,1000,10000.....

轉換10進制:  1,2,4,8....

也就是說,我們不需要每個都枚舉,假如有一個物品可以拿10個,那我們只需要將1,2,4,3這幾種情況跑一遍01就好了

 

可能還是有像我這樣的蒟蒻看不懂,我來證明一下(看的別的dalao的證明方法)

如果我們要湊的數n小於等於2^k(k是能拆最大的二進制數  以10為例,k就是2)轉換二進制直接湊都明白吧

如果n大於2^k怎么辦呢?

我們先想一個問題,假如你有2這個數,並且能湊出4這個數,那么是不是也能湊出4+2呢?

如果要湊n,我們可以先湊n-2^k,n-2^k肯定能湊出,而2^k也能湊出,那么n就能湊出

轉換部分

    for(i=1; i<=n; i++) {
        scanf("%d%d%d",&w[i],&v[i],&s);
        for(j=1; j<=s; j*=2) {
            w[++k] = w[i]*j;
            v[k] = v[i]*j;
            s-=j;
        }
        if(0!=s) {
            w[++k] = w[i]*s;
            v[k] = v[i]*s;
        }
    }

 

分組背包:

有好多組物品,每組只能拿一個物品,使總價值最大

其實這個很簡單,只需要再加一層循環枚舉組,使組內物品在不超出當前背包容積的情況下價值最大就好啦

    int zd2=0; 
    for(i=1; i<=zd; i++) {//枚舉組
        for(j=m; j>=1; j--) {//枚舉空間
            for(p=b[1].k ; p>=1; p--) {//枚舉組內每一物品
                if(w[b[i].bb[p]]<=j){//如果能裝下
                    if(zd2<a[j-w[b[i].bb[p]]]+v[b[i].bb[p]])//找最大的
                        zd2=a[j-w[b[i].bb[p]]]+v[b[i].bb[p]];
                } 
                    
            }
            a[j]=max(a[j],zd2);//在和原先的比較就好了
            zd2=0;
        }
    }

 

注意:最好不要在找最大時就對a[]做手腳,否則可能出錯(可以自己想下為什么)

當然,大家看到這個代碼肯定一臉懵,我在講一下預處理

這是定義的結構體

struct po {
    int bb[1001];//存放組內每一個物品的序號
    int k;//有多少物品(組內)
};
po b[101]= {0};//

 

輸入部分:

for(i=1; i<=n; i++) {
        scanf("%d%d%d",&w[i],&v[i],&zu);
        b[zu].bb[++b[zu].k] = i;//組內下一個物品編號
        if(zd<zu)//找出有多少組
            zd=zu;
    }

 

更新完啦!!!

 

時隔幾個月,我又來更新了....

至於樹形背包,前提是先學會分組背包

洛谷p2014選課為例

設dp[x][i]為以x為根的子樹,花i代價最大的價值,對於x的子樹都是葉子節點的情況應該很好理解

大家可以手動畫一下圖,這種情況很簡單,但問題是一般情況我們要解決的並不是這種問題

假設我們正在計算dp[x][k],樹根的費用是m

我們必須要選樹根,因為樹根不選是無法選兒子的

那么我們只需要計算出所有子樹中加起來費用是k - m的最大價值


免責聲明!

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



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