acm演算法之三大揹包問題

whitesorrow發表於2015-05-17

三大揹包問題

1.01揹包問題

有N件物品和一個容量為V的揹包。第i件物品的體積是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。

狀態轉移方程:

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

這個方程非常重要,基本上所有跟揹包相關的問題的方程都是由它衍生出來的

偽碼:

  for i=1..N

   for v=V..0

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

如果不放第i件物品,那麼問題就轉化為“前i-1件物品放入容量為v的揹包中”,

價值為f[i-1][v];

如果放第i件物品,那麼問題就轉化為“前i-1件物品放入剩下的容量為v-c[i]的揹包中”,

此時能獲得的最大價值就是f[i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。

 

例題:

Description

辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”

如果你是辰辰,你能完成這個任務嗎?

Input

輸入的第一行有兩個整數T(1 <= T <= 1000)和M(1 <= M <= 100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裡的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的整數,分別表示採摘某株草藥的時間和這株草藥的價值。

Output

輸出包括一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。

SampleInput

70 3

71 100

69 1

1 2

SampleOutput

3

解決方案:

#include<iostream>

# include<cstring>

# define max(a,b) a>b?a:b

using namespace std;

int main()

{

 

    int dp[101][1001],m,T,w[101],val[101],i,j;

    cin>>T>>m;

    for(i=1;i<=m;i++)

        cin>>w[i]>>val[i];

    memset(dp,0,sizeof(dp));

    for(i=1;i<=m;i++)

     for(j=0;j<=T;j++)      //j相當於上面說的V-c[i]

         {

    if(j>=w[i])

       dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+val[i]);//放還是不放的選擇

    else dp[i][j]=dp[i-1][j];

     }

     cout<<dp[m][T]<<endl;

     return 0;

}

*01揹包問題的一維優化

普通01揹包問題解決方案花費如下:

     時間複雜度為o(V * T) ,空間複雜度為o(V * T) 。 時間複雜度已經無法優化,但是空間複雜度則可以進行優化。但必須將V 遞減的方式進行遍歷,即V.......0 的方式進行。

初始化:

(1)若要求揹包必須放滿,則初始如下:

        f[0] = 0 , f[1...V]表示-INF。表示當容積為0時,只接受一個容積為0的物品入包。

(2)若要求揹包可以空下,則初始化如下:

        f[0...V] = 0 ,表示任意容積的揹包都有一個有效解即為0。   

具體解釋如下:

     初始化的f陣列事實上就是在沒有任何物品可以放入揹包時的合法狀態。

     如果要求揹包恰好裝滿,那麼此時只有容量為0的揹包可能被價值為0的nothing“恰好裝滿”,

     其它容量的揹包均沒有合法的解,屬於未定義的狀態,它們的值就都應該是-∞了。

     如果揹包並非必須被裝滿,那麼任何容量的揹包都有一個合法解“什麼都不裝”,

     這個解的價值為0,所以初始時狀態的值也就全部為0了。

相關程式碼及解釋:

/*

 01揹包,使用了優化後的儲存空間

 建立陣列

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

    將前i件物品,放入容量為v的揹包中的最大值。

下面介紹一個優化,使用一維陣列,來表示

(1) f[v]表示每一種型別的物品,在容量為v的情況下,最大值。

    但是體積迴圈的時候,需要從v----1迴圈遞減。

初始化問題:

(1)若要求揹包中不允許有剩餘空間,則可以將f[0]均初始化為0,其餘的f[1..n]均初始化為-INF 。

    表示只有當容積為0 的時候,允許放入質量為0的物品。

    而當容積不為0的情況下,不允許放入質量為0的物品,並且把狀態置為未知狀態。  

(2)若要求揹包中允許有剩餘空間 ,則可以將f[1n],均初始化為0。

   這樣,當放不下去的時候,可以空著。

&/

#include <iostream>

 using namespace std ;

 const int V = 1000 ;  //總的體積

 const int T = 5 ;    //物品的種類

 int f[V+1] ;

 //#define EMPTY                          //可以不裝滿

 int w[T] = {8 , 10 , 4 , 5 , 5};         //價值

 int c[T] = {600 , 400 , 200 , 200 , 300};//每一個的體積

 const int INF = -66536  ;

 

 int package()

 {

    #ifdefEMPTY

    for(int i = 0 ; i <= V ;i++) //條件編譯,表示揹包可以不儲存滿

      f[i] = 0 ;   

 #else

    f[0] = 0 ;

    for(int i = 1 ; i <= V ;i++) //條件編譯,表示揹包必須全部儲存滿

      f[i] = INF ;  

 #endif

   

    for(int i = 0 ; i < T ; i++)

    {

      for(int v = V ; v >= c[i] ;v--)      //必須全部從V遞減到0

         {             

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

//此f[v]實質上是表示的是i-1次之前的值。

         }                

    }

    return f[V] ;       

 }

 

 int main()

 {

     

   int temp = package() ;  

   cout<<temp<<endl     ;  

   system("pause")      ;

   return 0 ;   

 }

例題:

Description

Many years ago , in Teddy’shometown there was a man who was called “Bone Collector”. This man like tocollect varies of bones , such as dog’s , cow’s , also he went to the grave …

The bone collector had a bigbag with a volume of V ,and along his trip of collecting there are a lot ofbones , obviously , different bone has different value and different volume,now given the each bone’s value along his trip , can you calculate out themaximum of the total value the bone collector can get ?

Input

The first line contain ainteger T , the number of cases.

Followed by T cases , eachcase three lines , the first line contain two integer N , V, (N <= 1000 , V<= 1000 )representing the number of bones and the volume of his bag. And thesecond line contain N integers representing the value of each bone. The thirdline contain N integers representing the volume of each bone.

Output

One integer per linerepresenting the maximum of the total value (this number will be less than231).

SampleInput

1

5 10

1 2 3 4 5

5 4 3 2 1

SampleOutput

14

程式碼:

#include <iostream>

using namespace std;

 int main()

{

int nCases;

int nPack, nMaxVolume;

int weight[1002], value[1002];

int record[1002];

    //freopen("input.txt","r", stdin);

    scanf("%d", &nCases);

    while(nCases--)

    {

        memset(record, 0, sizeof(record));

        scanf("%d %d", &nPack,&nMaxVolume);

        for(int i=0; i<nPack; ++i)

            scanf("%d",&value[i]);

        for(int i=0; i<nPack; ++i)

            scanf("%d",&weight[i]);

        for(int i=0; i<nPack; ++i)

            for(int j=nMaxVolume; j>=weight[i]; --j)

            {

                if(record[j-weight[i]]+value[i]> record[j])

                    record[j] =record[j-weight[i]]+value[i];

            }

        printf("%d\n",record[nMaxVolume]);

    }

    return 0;

}

2.多重揹包

有N種物品和一個容量為V的揹包。第i種物品最多有num[i]件可用,每件費用是c[i],價值是w[i]。求解將哪些物品裝

入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

狀態轉移方程為:

例題

Description

急!災區的食物依然短缺!

為了挽救災區同胞的生命,心繫災區同胞的你準備自己採購一些糧食支援災區,現在假設你一共有資金n元,而市場有m種大米,每種大米都是袋裝產品,其價格不等,並且只能整袋購買。請問:你用有限的資金最多能採購多少公斤糧食呢?

Input

輸入資料首先包含一個正整數C,表示有C組測試用例,每組測試用例的第一行是兩個整數n和m(1<=n<=100, 1<=m<=100),分別表示經費的金額和大米的種類,然後是m行資料,每行包含3個數p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分別表示每袋的價格、每袋的重量以及對應種類大米的袋數。

Output

對於每組測試資料,請輸出能夠購買大米的最多重量,你可以假設經費買不光所有的大米,並且經費你可以不用完。每個例項的輸出佔一行。

SampleInput

1

8 2

2 100 4

4 100 2

SampleOutput

400

解決方案:

#include <iostream>

using namespace std;

int nCases;

int nValue, nKind;

int value[105], weight[105],bag[105];

int nMultiplePack[105];

int main()

{

    //freopen("input.txt","r", stdin);

    scanf("%d", &nCases);

    while(nCases--)

    {

        memset(nMultiplePack, 0,sizeof(nMultiplePack));

        scanf("%d %d", &nValue,&nKind);

        for(int i=0; i<nKind; ++i)

            scanf("%d %d %d",&value[i], &weight[i], &bag[i]);

        for(int i=0; i<nKind; ++i)

            for(int j=0; j<bag[i]; ++j)

                for(int k=nValue;k>=value[i]; --k)

                    if(nMultiplePack[k] <nMultiplePack[k-value[i]]+weight[i])

                        nMultiplePack[k] =nMultiplePack[k-value[i]] + weight[i];

        printf("%d\n",nMultiplePack[nValue]);

    }

    return 0;

}

3.完全揹包

問題:一個揹包總容量為V,現在有N個物品,第i個 物品體積為weight[i],價值為value[i],每個物品都有無限多件,現在往揹包裡面裝東西,怎麼裝能使揹包的內物品價值最大?

狀態轉移方程為:

f[i+1][j]=max(f[i][j-k*weight[i+1]+k*value[i+1]),其中0<=k<=V/weight[i+1]

解決方案:

#include<iostream> 

using namespace std; 

#define  V 1500 

unsigned int f[V];//全域性變數,自動初始化為0 

unsigned int weight[10]; 

unsigned int value[10]; 

#define  max(x,y)  (x)>(y)?(x):(y) 

int main() 

    int N,M; 

    cin>>N;//物品個數 

    cin>>M;//揹包容量 

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

    { 

       cin>>weight[i]>>value[i]; 

    } 

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

        for (int j=1; j<=M; j++) 

        { 

            if (weight[i]<=j) 

            { 

               f[j]=max(f[j],f[j-weight[i]]+value[i]); 

            }            

        } 

cout<<f[M]<<endl;//輸出最優解 

return 0;

例題:

Description

Before ACM can do anything, abudget must be prepared and the necessary financial support obtained. The mainincome for this action comes from Irreversibly Bound Money (IBM). The ideabehind is simple. Whenever some ACM member has any small money, he takes allthe coins and throws them into a piggy-bank. You know that this process isirreversible, the coins cannot be removed without breaking the pig. After asufficiently long time, there should be enough cash in the piggy-bank to payeverything that needs to be paid.

But there is a big problemwith piggy-banks. It is not possible to determine how much money is inside. Sowe might break the pig into pieces only to find out that there is not enoughmoney. Clearly, we want to avoid this unpleasant situation. The onlypossibility is to weigh the piggy-bank and try to guess how many coins areinside. Assume that we are able to determine the weight of the pig exactly andthat we know the weights of all coins of a given currency. Then there is someminimum amount of money in the piggy-bank that we can guarantee. Your task isto find out this worst case and determine the minimum amount of cash inside thepiggy-bank. We need your help. No more prematurely broken pigs!

Input

The input consists of T testcases. The number of them (T) is given on the first line of the input file.Each test case begins with a line containing two integers E and F. Theyindicate the weight of an empty pig and of the pig filled with coins. Bothweights are given in grams. No pig will weigh more than 10 kg, that means 1<= E <= F <= 10000. On the second line of each test case, there is aninteger number N (1 <= N <= 500) that gives the number of various coinsused in the given currency. Following this are exactly N lines, each specifyingone coin type. These lines contain two integers each, Pand W (1 <= P <=50000, 1 <= W <=10000). P is the value of the coin in monetary units, Wis it's weight in grams.

Output

Print exactly one line ofoutput for each test case. The line must contain the sentence "The minimumamount of money in the piggy-bank is X." where X is the minimum amount ofmoney that can be achieved using coins with the given total weight. If theweight cannot be reached exactly, print a line "This is impossible.".

SampleInput

3

10 110

2

1 1

30 50

10 110

2

1 1

50 30

1 6

2

10 3

20 4

SampleOutput

The minimum amount of money inthe piggy-bank is 60.

The minimum amount of money inthe piggy-bank is 100.

This is impossible.

解決方案:

// 初始化為無窮大

#include <iostream>

using namespace std;

 int nCases;

int nPack, nVolume1, nVolume2,nVolume;

int weight[510], value[510];

int record[10000];

const int INF = 1000000001;

int main()

{   

    //freopen("input.txt","r", stdin);

     scanf("%d", &nCases);

    while(nCases--)

    {

        scanf("%d %d", &nVolume1,&nVolume2);

        nVolume = nVolume2-nVolume1;

        scanf("%d", &nPack);

        for(int i=0; i<nPack; ++i)

        {

            scanf("%d %d",&value[i], &weight[i]);

        }

        for(int i=0; i<=nVolume; ++i)

            record[i] = INF;

        record[0] = 0;

        for(int i=0; i<nPack; ++i)

            for(int j=weight[i]; j<=nVolume; ++j)

               if(record[j]>record[j-weight[i]]+value[i])

                    record[j] =record[j-weight[i]]+value[i];

        if(record[nVolume] == INF)

            printf("This isimpossible.\n");

        else

            printf("The minimum amount ofmoney in the piggy-bank is %d.\n",record[nVolume]);

     }

    return 0;

}

// 這是ambition神牛的版本

// 初始化為-1

 

#include<stdio.h>

int num[10001],w[500],v[500];

main()

{

    int n,m,e,f,t,i,j;

    for(scanf("%d",&t);t>0;t--)

    {

        scanf("%d%d",&e,&f);

        m=f-e;

       for(scanf("%d",&n),i=0;i<n;i++)

           scanf("%d%d",&v[i],&w[i]);

        num[0]=0;

        for(i=1;i<=m;i++)

            num[i]=-1;

        for(i=0;i<n;i++)

        {

            for(j=w[i];j<=m;j++)

            {

               if(num[j-w[i]]!=-1&&num[j]!=-1)

                {if(num[j-w[i]]+v[i]<num[j])num[j]=num[j-w[i]]+v[i];}

                elseif(num[j-w[i]]!=-1&&num[j]==-1)

                {num[j]=num[j-w[i]]+v[i];}

            }

        }

        if(num[m]!=-1)

        printf("The minimum amount ofmoney in the piggy-bank is %d.\n",num[m]);

        else

            printf("This isimpossible.\n");

    }

}

*總結

首先我們把三種情況放在一起來看:

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

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

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

比較三個題目,會發現不同點在於每種揹包的數量,01揹包是每種只有一件,完全揹包是每種無限件,而多重揹包是每種有限件。

先來分析01揹包:

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

用子問題定義狀態:即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]的值就是哪種。

這裡是用二位陣列儲存的,可以把空間優化,用一位陣列儲存。

用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[v]表示當前狀態是容量為v的揹包所得價值,而又使f[v]和f[v-c[i]]+w[i]標籤前一狀態的價值

逆序!這就是關鍵!

for i=1..N

   for v=V..0

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

分析上面的程式碼:當內迴圈是逆序時,就可以保證後一個f[v]和f[v-c[i]]+w[i]是前一狀態的!

完全揹包:

完全揹包按其思路仍然可以用一個二維陣列來寫出:

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

同樣可以轉換成一維陣列來表示:虛擬碼如下:

for i=1..N

    for v=0..V

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

順序!

想必大家看出了和01揹包的區別,這裡的內迴圈是順序的,而01揹包是逆序的。

現在關鍵的是考慮:為何完全揹包可以這麼寫?

在次我們先來回憶下,01揹包逆序的原因?是為了是max中的兩項是前一狀態值,這就對了。

那麼這裡,我們順序寫,這裡的max中的兩項當然就是當前狀態的值了,為何?

因為每種揹包都是無限的。當我們把i從1到N迴圈時,f[v]表示容量為v在前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]件。


相關文章