揹包問題

揹包問題有三種:
1. 01揹包: 每件物品只能放一次
2. 完全揹包:每種物品可以放無限多次
3. 多重揹包:每件物品可以放有限次數

01揹包

1)問題描述:**有n 個物品,它們有各自的重量和價值,現有給定容量的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?(一個物品選一次)

在這裏插入圖片描述

(2)遞推關係式:
  1. j<w(i): V(i,j)=V(i-1,j)
  2. j>=w(i): V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
(3)優化空間:
/*
01揹包
適用於輸入格式如下的問題,出現問題請自行調整:
第一行兩個整數M, N分別表示揹包空間與物品總數;
第2到N+1行每行兩個整數,分別表示這類物品每個的價值和所需空間。
*/
#include<cstdio>
const int MAXM=10001,MAXN=51;
int m,n;
int w[MAXN],c[MAXN];
int f[MAXM];
int max(int x,int y)
{
    return x>y?x:y;
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--) //逆序
            f[j]=max(f[j],f[j-w[i]]+c[i]);
    printf("%d\n",f[m]);
    return 0;
}

在這裏插入圖片描述

完全揹包

(1)問題描述

完全揹包和01揹包類似,只不過每種物品有無限多件,你愛取幾件取幾件!它們的代碼也是極其的相似!僅僅是for循環的順序從逆序變成了順序。

(2)遞推關係
          f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
(3)代碼:

爲什麼是順序? - 因爲每種物品的數目是無限的,我們是在當前揹包裏的物品的基礎上繼續加當前物品,有可能揹包裏已經有這種物品了。所以我們只需要考慮當前狀態。
在這裏插入圖片描述

/*
完全揹包,輸入規則:
第一行兩個整數M, N分別表示揹包空間與物品總數;
第2到N+1行每行兩個整數,分別表示這類物品每個的價值和所需空間。
*/
#include<cstdio>
const int MAXM=10001,MAXN=51;
int m,n;
int w[MAXN],c[MAXN];
int f[MAXM];
int max(int x,int y)
{
    return x>y?x:y;
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int j=w[i];j<=m;j++) //順序
            f[j]=max(f[j],f[j-w[i]]+c[i]);
    printf("%d\n",f[m]);
    return 0;
}      

在這裏插入圖片描述

多重揹包

(1)問題描述

多重揹包是每個物品有不同的個數限制,如第i個物品個數爲num[i]。。

多重揹包問題的特點是數據量大,若按照01揹包的做法開dp[ m ] [ n ]的數組進行遍歷必會超時,所以建立數組時開設dp[ maxn ](maxn爲數據可能達到的最大值)。

(2)遞推關係

同樣可以用f[i][j]表示前i間物品恰放入一個容器爲j的揹包可以獲得的最大價值,且每個物品數量不超多num[i]。則其狀態轉移方程爲:

f[i][j] = max{f[i-1][j-kweight[i]]+kvalue[i]} ,
其中(0<=k<=min{j/weight[i], num[i]})

(3)代碼

多重揹包問題限定了一種物品的個數,解決多重揹包問題,只需要把它轉化爲0-1揹包問題即可。比如,有2件價值爲5,重量爲2的同一物品,我們就可以分爲物品a和物品b,a和b的價值都爲5,重量都爲2,但我們把它們視作不同的物品。

存在問題:空間不夠

#include <iostream>
using namespace std;
#define V 1000
int weight[50 + 1];
int value[50 + 1];
int num[20 + 1];
int f[V + 1];
int max(int a, int b) {
    return a > b ? a : b;
}
int main() {
    int n, m;
    cout << "請輸入物品個數:";
    cin >> n;
    cout << "請分別輸入" << n << "個物品的重量、價值和數量:" << endl;
    for (int i = 1; i <= n; i++) {
        cin >> weight[i] >> value[i] >> num[i];
    }
    int k = n + 1;
    for (int i = 1; i <= n; i++) {
        while (num[i] != 1) {
            weight[k] = weight[i];
            value[k] = value[i];
            k++;
            num[i]--;
        }
    }
    cout << "請輸入揹包容量:";
    cin >> m;
    for (int i = 1; i <= k; i++) {
        for (int j = m; j >= 1; j--) {
            if (weight[i] <= j) f[j] = max(f[j], f[j - weight[i]] + value[i]);
        }
    }
    cout << "揹包能放的最大價值爲:" << f[m] << endl;
}
(4)優化空間

http://blog.csdn.net/chuck001002004/article/details/50340819

初始化將數組dp[ ]全部設爲0,將dp[ 0 ]設爲1。利用雙重循環 i 從1到n遍歷w[ i ],內層循環 j 從v[ i ]開始往後遍歷,只要dp[ j - v[ i ] ]值爲真(即表示價值j-v[ i ]能夠滿足)且dp[ j ]值爲假(表示價值 j 尚未被滿足)則價值 j 是有可能達到的。爲什麼說有可能?是因爲能否達到價值 j 也得看v[ i ]的數量是否達到上限。如何記錄w[ i ]的數量呢?還是要開設一個專門記錄個數的數組num[ maxn ],在第一層循環內將數組num[ ]初始化爲0,一旦滿足 dp[ j - v[ i ] ]&&!dp[ j ]&&num[ j - v[ i ] ]<w[ i ] 則說明價值 j 是可以滿足的,則將dp[ j ]的值設爲真,再將num[ j ]=num[ j - v[ i ] ]+1 表示價值 j 所對應的價值爲v[ i ]的物品的使用數在價值爲 j-v[ i ]的基礎上加1,此步操作尤爲關鍵!之後根據題意看求什麼邊操作即可。
在這裏插入圖片描述

(5)類似問題

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

B.n種價值的硬幣,每種都有其數量w[ i ],給一個最大價值量m求出不超過最大價值量的情況下能湊出多少種價值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章