【動態規劃】揹包問題(一) 01揹包 徹底揹包 多重揹包

 

1、01揹包算法

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

這是最基礎的揹包問題,總的來講就是:選仍是不選,這是個問題<( ̄ˇ ̄)/app

至關於用f[i][j]表示前i個揹包裝入容量爲v的揹包中所能夠得到的最大價值。優化

對於一個物品,只有兩種狀況spa

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

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

狀態轉移方程爲:f[i][v] = max(f[i-1][v], f[i-1][v-w[i]]+c[i])ip

一道裸01揹包題↓_↓it

採藥io

題目描述  Description

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

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

輸入描述  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<stdio.h>
#include<algorithm>
using namespace std;
int f[1001][1001];
int main()
{
    int T, n,c[10001], v[10001];
     scanf("%d%d", &T, &n);
     for(int i = 1; i <= n; i++)
       scanf("%d%d", &v[i], &c[i]);
     for(int i = 1; i <= n; i++)
     {
         for(int j = 0; j <= T; j++)
            f[i][j] = f[i-1][j];
          for(int j = 0; j+v[i] <= T; j++)
           f[i][j] = max(f[i][j] + c[i], f[i-1][j+v[i]]);
     }
     int ans = 0;
     for(int i = 0; i <= T; i++)
       ans = max(ans, f[n][i]);
     printf("%d", ans);
     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;
}

2、徹底揹包

有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;
}

3、多重揹包

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

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

同理,咱們能夠得出狀態轉移方程:f[i][v]=max(f[i-1][v-k*w[i]]+ k*c[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;
} 

進行二進制優化,轉換爲01揹包(拆分物品)

#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;
}

以上就是三種基本的揹包問題(*^__^*)

 

 

 p.s.這裏初學鹹魚,若有錯誤歡迎各位大佬們指出~

相關文章
相關標籤/搜索