編程訓練——01揹包算法的兩種解法

題目

nn個物品,重量和價值分別存儲在數組ww和數組vv中,輸入總重量WW,求選出不超過WW的物品最大總價值。
每個物品只能選擇一次。

nwvn,w,v,均爲11001~100WW1100001~10000.

樣例輸入

4
2 1 3 2
3 2 4 2
5

n=4n=4個物品,重量ww分別是21322,1,3,2,價值vv分別是32423,2,4,2,揹包重量WW55.

樣例輸出

7

選出0130,1,3號物品,剛好裝滿揹包,且總價值最大,爲77.

算法1

dp[i][j]dp[i][j]表示從前ii個物品(0i10~i-1號)中選出重量不超過jj的總價值。如果不選擇第ii個物品,則dp[i][j]=dp[i1][j]dp[i][j]=dp[i-1][j];如果選擇第ii個物品,則dp[i][j]=dp[i1][jw[i1]]+v[i1]dp[i][j]=dp[i-1][j-w[i-1]]+v[i-1](不過要注意判斷jw[i1]j≥w[i-1],如果不成立說明揹包裝不下第ii個物品),這樣可得遞推關係:
dp[i][j]={dp[i][j]=maxdp[i1][j],dp[i1][jw[i1]]+v[i1],jw[i1]dp[i][j]=dp[i1][j],j<w[i] dp[i][j]=\left\{ \begin{aligned} dp[i][j]=max{dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]},j≥w[i-1]\\ dp[i][j]=dp[i-1][j],j<w[i] \end{aligned} \right.
其中初始值爲dp[0][j]=0dp[0][j]=0

最終輸出dp[n][W]dp[n][W]

由於要從dp[0][0]dp[0][0]更新到dp[n][W]dp[n][W],所以複雜度爲O(nW)O(nW)

代碼1

#include<stdio.h>
#include<math.h>

#define maxn 105

int max(int a, int b){
    return (a>b)?a:b;
}

int main(){
    int n, W;
    int w[maxn], v[maxn];
    int dp[maxn][maxn];
    while(scanf("%d %d", &n, &W)==2){
        for(int i = 1;i <= n;i++){
            scanf("%d", w + i);
        }
        for(int i = 1;i <= n;i++){
            scanf("%d", v + i);
        }
        for(int j = 0;j <= W;j++){
            dp[0][j] = 0;
        }
        for(int i = 1;i <= n;i++){
            dp[i][0] = 0;
        }
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= W;j++){
                if(w[i]>j)dp[i][j] = dp[i-1][j];
                else
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
            }
        }
        printf("%d\n", dp[n][W]);
    }

    return 0;
}

算法2

如果改變題目對輸入數據的約束:
nvn,v,均爲11001~100ww11071~10^7WW11091~10^9.

已知複雜度爲O(nW)O(nW),而上述W非常大,這會導致複雜度很高。

不妨改變dp[i][j]dp[i][j]爲前ii個物品(0i10~i-1號)中選出價值爲jj的最小重量,則需要從dp[0][0]dp[0][0]更新到dp[n][]dp[n][所有物品總價值],然後輸出滿足dp[n][j]<Wdp[n][j]<Wjj的最大值。所有物品的總價值不會超過Σi=0i=n1v[i]<=10000\Sigma_{i=0}^{i=n-1}v[i]<=10000,複雜度就降下來了。

如果不選擇第i個物品,則dp[i][j]對應從前i1i-1個物品中選出價值爲jj,即dp[i1][j]dp[i-1][j];如果選擇第ii個物品,則dp[i][j]dp[i][j]對應dp[i1][jv[i1]]+w[i1]dp[i-1][j-v[i-1]]+w[i-1](記得先判斷jv[i1]j≥v[i-1]),這樣得到遞推關係:
dp[i][j]={dp[i][j]=mindp[i1][j],dp[i1][jv[i1]]+w[i1],jv[i1]dp[i][j]=dp[i1][j],j<v[i] dp[i][j]=\left\{ \begin{aligned} dp[i][j]=min{dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]},j≥v[i-1]\\ dp[i][j]=dp[i-1][j],j<v[i] \end{aligned} \right.
初始值爲dp[0][0]=0dp[0][0]=0dp[0][]=INFdp[0][非零]=INF。輸出滿足dp[n][j]<Wdp[n][j]<Wjj的最大值。

#include<stdio.h>

#define maxn 105
#define INF 1000000005	// 所有物品總重最多爲10億
#define maxv 105

int min(int a, int b){
    return (a<b)?a:b;
}

int main(){
    int n, W;
    int v[maxn], w[maxn];
    int dp[maxn][maxn * maxv];
    int totalvalue, res;
    while(scanf("%d", &n)==1){
        for(int i = 0;i < n;i++){
            scanf("%d", &w[i]);
        }
        totalvalue = 0;
        for(int i = 0;i < n;i++){
            scanf("%d", &v[i]);
            totalvalue += v[i];
        }
        scanf("%d", & W);
        for(int j = 1;j <= totalvalue;j++){
            dp[0][j] = INF;
        }
        dp[0][0] = 0;
        for(int i = 1;i <= n;i++){
            for(int j = 0;j <= totalvalue;j++){
                if(j>=v[i-1]){
                    dp[i][j] = min(dp[i-1][j],dp[i-1][j-v[i-1]]+w[i-1]);
                }
                else dp[i][j] = dp[i-1][j];
            }
        }

        // 輸出dp[n][j]中不超過W的最大的j
        res = 0;
        for(int j = totalvalue;j >= 0;j--){
            if(dp[n][j]<=W){
                res = j;
                break;
            }
        }
        printf("%d\n", res);
    }

    return 0;
}
發佈了40 篇原創文章 · 獲贊 43 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章