揹包問題(0-1揹包,徹底揹包,多重揹包知識概念詳解)

 

經過:http://www.cnblogs.com/tanky_woo/archive/2010/07/31/1789621.html  php

   http://blog.csdn.net/lyhvoyage/article/details/8545852改編而來html

 

3種揹包的簡單概念:

0-1揹包  (ZeroOnePack): 有N件物品和一個容量爲V的揹包。每種物品均只有一件ios

             第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使價值總和最大。算法

徹底揹包(CompletePack): 有N種物品和一個容量爲V的揹包,每種物品都有無限件可用數組

              第i種物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。學習

多重揹包   (MultiplePack): 有N種物品和一個容量爲V的揹包。第i種物品最多有n[i]件可用測試

                每件費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。優化

比較三個題概念,會發現不一樣點在於每種揹包的數量,01揹包是每種只有一件,徹底揹包是每種無限件,而多重揹包是每種有限件。spa

——————————————————————————————————————————————————————————–.net

1、0-1揹包:

有N件物品和一個容量爲V的揹包。(每種物品均只有一件)第i件物品的費用是c[i],價值是w[i]。求解將哪些物品裝入揹包可以使價值總和最大。

這是最基礎的揹包問題,特色是:每種物品僅有一件,能夠選擇放或不放。

用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量爲v的揹包能夠得到的最大價值。則其狀態轉移方程即是:

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

把這個過程理解下:在前i件物品放進容量v的揹包時,

它有兩種狀況:

第一種是第i件不放進去,這時所得價值爲:f[i-1][v]

第二種是第i件放進去,這時所得價值爲:f[i-1][v-c[i]]+w[i]

(第二種是什麼意思?就是若是第i件放進去,那麼在容量v-c[i]裏就要放進前i-1件物品)

最後比較第一種與第二種所得價值的大小,哪一種相對大,f[i][v]的值就是哪一種。

(這是基礎,要理解!)

 

V=10,N=3,c[]={3,4,5}, w={4,5,6}

(1)揹包不必定裝滿

      計算順序是:從右往左,自上而下:由於每一個物品只能放一次,前面的體積小的會影響體積大的

      

(2)揹包恰好裝滿    

      計算順序是:從右往左,自上而下。注意初始值,其中-inf表示負無窮

      

這裏是用二位數組存儲的,能夠把空間優化,用一位數組存儲。

用f[0..v]表示,f[v]表示把前i件物品放入容量爲v的揹包裏獲得的價值。把i從1~n(n件)循環後,最後f[v]表示所求最大值。

*這裏f[v]就至關於二位數組的f[i][v]。那麼,如何獲得f[i-1][v]和f[i-1][v-c[i]]+w[i]?(重點!思考)
首先要知道,咱們是經過i從1到n的循環來依次表示前i件物品存入的狀態。即:for 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-c[i]]兩個子問題遞推而來,可否保證在推f[i][v]時(也即在第i次主循環中推f[v]時)可以獲得f[i-1][v]和f[i-1][v-c[i]]的值呢?

事實上,這要求在每次主循環中咱們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]保存的是狀態f[i-1][v-c[i]]的值

 代碼以下:

for i=1..N
   for v=V..0
        f[v]=max{f[v],f[v-c[i]]+w[i]};
測試數據:
10,3
3,4
4,5
5,6

         

這個圖表畫得很好,藉此來分析:

C[v]從物品i=1開始,循環到物品3,期間,每次逆序獲得容量v在前i件物品時能夠獲得的最大值。(請在草稿紙上本身畫一畫

 

這裏以一道題目來具體看看:

 

題目:http://acm.hdu.edu.cn/showproblem.php?pid=2602

 

代碼在這裏:http://www.wutianqi.com/?p=533

 

 

2、徹底揹包:

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

且價值總和最大。

徹底揹包按其思路仍然能夠用一個二維數組來寫出:

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

若是將v的循環順序從上面的0-1揹包逆序改爲順序的話,那麼則成了f[i][v]由f[i][v-c[i]]推知,恰好知足徹底揹包的定義能夠將數組降維  

那麼這裏,咱們順序寫,這裏的max中的兩項固然就是當前狀態的值了,由於每種揹包都是無限的。當咱們把i從1到N循環時,

f[v]表示容量爲v在前i種揹包時所得的價值,這裏咱們要添加的不是前一個揹包,而是當前揹包。因此咱們要考慮的固然是當前狀態。

 

V=10,N=3,c[]={3,4,5}, w={4,5,6}

(1)揹包不必定裝滿

  計算順序是:從左往右,自上而下:  每一個物品能夠放屢次,前面的會影響後面的

     

(2)揹包恰好裝滿

  計算順序是:從左往右,自上而下。注意初始值,其中-inf表示負無窮

     

代碼以下:

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-c[i]]+w[i]}

這裏一樣給你們一道題目:

題目:http://acm.hdu.edu.cn/showproblem.php?pid=1114

代碼:http://www.wutianqi.com/?p=535

(分析代碼也是學習算法的一種途徑,有時並不必定要看算法分析,結合題目反而更容易理解。)

一個簡單有效的優化 
徹底揹包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j知足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。這個優化的正確性顯然:任何狀況下均可將價值小費用高得j換成物美價廉的i,獲得至少不會更差的方案。對於隨機生成的數據,這個方法每每會大大減小物品的件數,從而加快速度。然而這個並不能改善最壞狀況的複雜度,由於有可能特別設計的數據能夠一件物品也去不掉。 

——————————————————————————————————————————————————————————–

 

3、多重揹包

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

且價值總和最大。

這題目和徹底揹包問題很相似。基本的方程只需將徹底揹包問題的方程略微一改便可,由於對於第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。令f[i][v]表示前i種物品恰放入一個容量爲v的揹包的最大權值,則有狀態轉移方程:

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

這裏一樣轉換爲01揹包:

普通的轉換對於數量較多時,則可能會超時

對於普通的。就是多了一箇中間的循環,把j=0~bag[i],表示把第i中揹包從取0件枚舉到取bag[i]件。

給出一個例題:

題目:http://acm.hdu.edu.cn/showproblem.php?pid=2191

代碼:http://www.wutianqi.com/?p=537

 

對於二進制辦法

多重揹包轉換成 01 揹包問題就是多了個初始化,把它的件數C 用二進制分解成若干個件數的集合,這裏面數字能夠組合成任意小於等於C的件數,並且不會重複,

之因此叫二進制分解,是由於這樣分解能夠用數字的二進制形式來解釋  
       好比:7的二進制 7 = 111 它能夠分解成 001 010 100 這三個數能夠組合成任意小於等於7 的數,並且每種組合都會獲得不一樣的數  
       15 = 1111 可分解成 0001  0010  0100  1000 四個數字  
       若是13 = 1101 則分解爲 0001 0010 0100 0110 前三個數字能夠組合成  7之內任意一個數,即一、二、4能夠組合爲1——7內全部的數,加上 0110 = 6 能夠組合成任意一個大於6 小於等於13的數,好比12,可讓前面貢獻6且後面也貢獻6就好了。雖然有重複但老是能把 13 之內全部的數都考慮到了,基於這種思想去把多件物品轉換爲,多種一件物品,就可用01 揹包求解了。

 

int n;  //輸入有多少種物品
int c;  //每種物品有多少件
int v;  //每種物品的價值
int s;  //每種物品的尺寸
int count = 0; //分解後可獲得多少種物品
int value[MAX]; //用來保存分解後的物品價值
int size[MAX];  //用來保存分解後物品體積

scanf("%d", &n);    //先輸入有多少種物品,接下來對每種物品進行分解

while (n--)     //接下來輸入n中這個物品
{
    scanf("%d%d%d", &c, &s, &v);  //輸入每種物品的數目和價值
    for (int k=1; k<=c; k<<=1)   //<<右移 至關於乘二
    {
        value[count] = k*v;
        size[count++] = k*s;
        c -= k;
    }
    if (c > 0)
    {
        value[count] = c*v;
        size[count++] = c*s;
    }
}

 

定理:一個正整數n能夠被分解成1,2,4,…,2^(k-1),n-2^k+1(k是知足n-2^k+1>0的最大整數)的形式,且1~n以內的全部整數都可以惟一表示成1,2,4,…,2^(k-1),n-2^k+1中某幾個數的和的形式。

 

證實以下:

(1) 數列1,2,4,…,2^(k-1),n-2^k+1中全部元素的和爲n,因此若干元素的和的範圍爲:[1, n];

(2)若是正整數t<= 2^k – 1,則t必定能用1,2,4,…,2^(k-1)中某幾個數的和表示,這個很容易證實:咱們把t的二進制表示寫出來,很明顯,t能夠表示成n=a0*2^0+a1*2^1+…+ak*2^(k-1),其中ak=0或者1,表示t的第ak位二進制數爲0或者1.

(3)若是t>=2^k,設s=n-2^k+1,則t-s<=2^k-1,於是t-s能夠表示成1,2,4,…,2^(k-1)中某幾個數的和的形式,進而t能夠表示成1,2,4,…,2^(k-1),s中某幾個數的和(加數中必定含有s)的形式。

(證畢!)

如今用count 代替 n 就和01 揹包問題徹底同樣了  

杭電2191題解:http://acm.hdu.edu.cn/showproblem.php?pid=2191

此爲多重揹包用01和徹底揹包:

 1 #include<stdio.h>
 2 #include<string.h>
 3 int dp[102];
 4 int p[102],h[102],c[102];
 5 int n,m;
 6 void comback(int v,int w)//經費,重量。徹底揹包;
 7 {
 8     for(int i=v; i<=n; i++)
 9         if(dp[i]<dp[i-v]+w)
10             dp[i]=dp[i-v]+w;
11 }
12 void oneback(int v,int w)//經費,重量;01揹包;
13 {
14     for(int i=n; i>=v; i--)
15         if(dp[i]<dp[i-v]+w)
16             dp[i]=dp[i-v]+w;
17 }
18 int main()
19 {
20     int ncase,i,j,k;
21     scanf("%d",&ncase);
22     while(ncase--)
23     {
24         memset(dp,0,sizeof(dp));
25         scanf("%d%d",&n,&m);//經費,種類;
26         for(i=1; i<=m; i++)
27         {
28             scanf("%d%d%d",&p[i],&h[i],&c[i]);//價值,重量,數量;
29             if(p[i]*c[i]>=n) comback(p[i],h[i]);
30             else
31             {
32                 for(j=1; j<c[i]; j<<1)
33                 {
34                     oneback(j*p[i],j*h[i]);
35                     c[i]=c[i]-j;
36                 }
37                 oneback(p[i]*c[i],h[i]*c[i]);
38             }
39         }
40         printf("%d\n",dp[n]);
41     }
42     return 0;
43 }

只是用01揹包,用二進制優化:

 1 #include <iostream>
 2 using namespace std;
 3 int main()
 4 {
 5     int nCase,Limit,nKind,i,j,k,  v[111],w[111],c[111],dp[111];
 6     //v[]存價值,w[]存尺寸,c[]存件數
 7     //在本題中,價值是米的重量,尺寸是米的價格
 8     int count,Value[1111],size[1111];
 9     //count存儲分解完後的物品總數
10     //Value存儲分解完後每件物品的價值
11     //size存儲分解完後每件物品的尺寸
12     cin>>nCase;
13     while(nCase--)
14     {
15         count=0;
16         cin>>Limit>>nKind;
17         for(i=0; i<nKind; i++)
18         {
19             cin>>w[i]>>v[i]>>c[i];
20             //對該種類的c[i]件物品進行二進制分解
21             for(j=1; j<=c[i]; j<<=1)
22             {
23                 //<<右移1位,至關於乘2
24                 Value[count]=j*v[i];
25                 size[count++]=j*w[i];
26                 c[i]-=j;
27             }
28             if(c[i]>0)
29             {
30                 Value[count]=c[i]*v[i];
31                 size[count++]=c[i]*w[i];
32             }
33         }
34         //通過上面對每一種物品的分解,
35         //如今Value[]存的就是分解後的物品價值
36         //size[]存的就是分解後的物品尺寸
37         //count就至關於原來的n
38         //下面就直接用01揹包算法來解
39         memset(dp,0,sizeof(dp));
40         for(i=0; i<count; i++)
41             for(j=Limit; j>=size[i]; j--)
42                 if(dp[j]<dp[j-size[i]]+Value[i])
43                     dp[j]=dp[j-size[i]]+Value[i];
44 
45         cout<<dp[Limit]<<endl;
46     }
47     return 0;
48 }

未優化的

 1 #include <iostream>
 2 using namespace std;
 3 int main()
 4 {
 5     int nCase,Limit,nKind,i,j,k,  v[111],w[111],c[111],dp[111];
 6     //v[]存價值,w[]存尺寸,c[]存件數
 7     //在本題中,價值是米的重量,尺寸是米的價格
 8     int count,Value[1111],size[1111];
 9     //count存儲分解完後的物品總數
10     //Value存儲分解完後每件物品的價值
11     //size存儲分解完後每件物品的尺寸
12     cin>>nCase;
13     while(nCase--)
14     {
15         count=0;
16         cin>>Limit>>nKind;
17         for(i=0; i<nKind; i++)
18         {
19             cin>>w[i]>>v[i]>>c[i];
20             //對該種類的c[i]件物品進行二進制分解
21             for(j=1; j<=c[i]; j<<=1)
22             {
23                 //<<右移1位,至關於乘2
24                 Value[count]=j*v[i];
25                 size[count++]=j*w[i];
26                 c[i]-=j;
27             }
28             if(c[i]>0)
29             {
30                 Value[count]=c[i]*v[i];
31                 size[count++]=c[i]*w[i];
32             }
33         }
34         //通過上面對每一種物品的分解,
35         //如今Value[]存的就是分解後的物品價值
36         //size[]存的就是分解後的物品尺寸
37         //count就至關於原來的n
38         //下面就直接用01揹包算法來解
39         memset(dp,0,sizeof(dp));
40         for(i=0; i<count; i++)
41             for(j=Limit; j>=size[i]; j--)
42                 if(dp[j]<dp[j-size[i]]+Value[i])
43                     dp[j]=dp[j-size[i]]+Value[i];
44 
45         cout<<dp[Limit]<<endl;
46     }
47     return 0;
48 }

由於限於我的的能力,我只能講出個大概,請你們具體仍是好好看看dd大牛的《揹包九講》。

暫時講完後,隨着之後更深刻的瞭解,我會把資料繼續完善,供你們一塊兒學習探討。

本文下載地址(點擊此處下載):Word版

相關文章
相關標籤/搜索