1. 程式人生 > >揹包問題(01揹包 完全揹包 多重揹包)

揹包問題(01揹包 完全揹包 多重揹包)

今天刷牛客網題目,用到揹包問題的求解。在這裡總結一下揹包問題。

揹包問題主要分為以下三種:

  • 01揹包
  • 完全揹包
  • 多重揹包

01揹包問題

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

這是我們最常遇見的揹包問題,其實只要弄清楚狀態轉移方程即可。

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

來解釋一下這個方程:

  • 情況一: 第i件不放進去,這時所得價值為:f[i-1][v]

  • 情況二: 第i件放進去,這時所得價值為:f[i-1][v-c[i]]+w[i]

我們來舉個例子加深一下理解:藍橋杯練習中的採藥問題。

辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”
如果你是辰辰,你能完成這個任務嗎?
輸入描述 Input Description
輸入第一行有兩個整數T(1<=T<=1000)和M(1<=M<=100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裡的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
輸出描述 Output Description
輸出包括一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。
Sample Input
70 3
71 100
69 1
1 2
樣例輸出 Sample Output
3
資料範圍及提示 Data Size & Hint
【資料規模】
對於30%的資料,M<=10;
對於全部的資料,M<=100。

#include <iostream> 
using namespace std; 
int dp[101][1001]; 
int main() 
{    
int t, m;    
cin >> t >> m;    
for(int i = 1; i <= m; i++) 
{        
int a, b;        
cin >> a >> b;        
    for(int j = 1; j <= t; j++) 
    { 
    if(j >= a)                
    dp[
i][j] = max(dp[i-1][j], dp[i-1][j-a] + b); else dp[i][j] = dp[i-1][j]; } } cout << dp[m][t]; return 0; }

如果我們用一維陣列來解題,狀態壓縮後:
設 f[v]表示重量不超過v公斤的最大價值, 則f[v]=max(f[v],f[v-w[i]]+c[i]) ,當v>=w[i],1<=i<=n 。

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxm = 2001, maxn = 101;
int m, n;
int w[maxn], c[maxn];
int f[maxm]; 
int main()
{
    scanf("%d%d",&m, &n);            //揹包容量m和物品數量n
    for (int i=1; i <= n; i++)
        scanf("%d%d",&w[i],&c[i]);     //每個物品的重量和價值
    for (int i=1; i <= n; i++)             //設f(v)表示重量不超過v公斤的最大價值
        for (int v = m; v >= w[i]; v--)  //注意是逆序
            f[v] = max(f[v-w[i]]+c[i], f[v]);
    printf("%d\n",f[m]);                      // f(m)為最優解
    return 0;
}

完全揹包問題

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

完全揹包和01揹包十分相像, 區別就是完全揹包物品有無限件。由之前的選或者不選轉變成了選或者不選,選幾件。

和01揹包一樣,我們可以寫出狀態轉移方程:

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

還有一個簡單的優化↓_↓

當一個物品的價值小於另一個物品的價值,但是價格高於另一個物品,我們就可以不去考慮這個物品。即若兩件物品i、j滿足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。我們為什麼要買一個又貴又難吃的東西呢(╯▽╰)

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxm=2001,maxn=101;
int n,m,v,i;
int c[maxn],w[maxn];
int f[maxm];
int main()
{
    scanf("%d%d",&m,&n);            //揹包容量m和物品數量n
    for(i=1;i<=n;i++) 
        scanf("%d%d",&w[i],&c[i]);
    for(i=1;i<=n;i++)
        for(v=w[i]; v<=m; v++)          //設 f[v]表示重量不超過v公斤的最大價值
                                        //這裡是v++ 順序 區別於01揹包 
            f[v]=max(f[v-w[i]]+c[i], f[v]);
    printf("%d\n", f[m]);           // f[m]為最優解
    return 0;
}

多重揹包問題

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

這裡又多了一個限制條件,每個物品規定了可用的次數。

同理,我們可以得出狀態轉移方程:f[i][v]=max(f[i-1][v-kw[i]]+ kc[i]|0<=k<=n[i])

【問題描述】

為了慶賀班級在校運動會上取得全校第一名成績,班主任決定開一場慶功會,為此撥款購買獎品犒勞運動員。期望撥款金額能購買最大價值的獎品,可以補充他們的精力和體力。

【輸入格式】

第一行二個數n(n<=500),m(m<=6000),其中n代表希望購買的獎品的種數,m表示撥款金額。
接下來n行,每行3個數,v、w、s,分別表示第I種獎品的價格、價值(價格與價值是不同的概念)和購買的數量(買0件到s件均可),其中v<=100,w<=1000,s<=10。

【輸出格式】

第一行:一個數,表示此次購買能獲得的最大的價值(注意!不是價格)。

【輸入樣例】

5 1000

80 20 4

40 50 9

30 50 7

40 30 6

20 20 1

【輸出樣例】

1040

#include<stdio.h>
#include<algorithm>
using namespace std;
int v[6002], w[6002], s[6002];
int f[6002];
int n, m;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i++)
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    for (int i = 1; i <= n; i++)
       for (int j = m; j >= 0; j--)
          for (int k = 0; k <= s[i]; k++)
          {
               if (j-k*v[i]<0) break;
               f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
          }
    printf("%d\n",f[m]);
    return 0;
}

dp壓縮後

#include<stdio.h>
#include<algorithm>
using namespace std;
int v[10001],w[10001];
int f[6001];
int n,m,n1;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x,y,s,t=1;
        scanf("%d%d%d",&x,&y,&s);
        while (s>=t) 
        {
            v[++n1]=x*t;
            w[n1]=y*t;
            s-=t;
            t*=2;
        }
        v[++n1]=x*s;
        w[n1]=y*s;                             //把s以2的指數分堆:1,2,4,…,2^(k-1),s-2^k+1,
    }
for(int i=1;i<=n1;i++)
        for(int j=m;j>=v[i];j--)
           f[j]=max(f[j],f[j-v[i]]+w[i]); 
    printf("%d\n",f[m]);
    return 0;
}

記得去複習這個部落格的講解啊:
http://www.cnblogs.com/fengziwei/p/7750849.html