1. 程式人生 > >01背包問題

01背包問題

兩個 其他 最優 mes else clu clas 返回 div

題目描述:

有n個重量和價值分別為wi,vi的物品。從這些物品中挑選出總重量不超過W的物品,求所有挑選方案中價值總和的最大值。(n>=1&&n<=100;wi,vi>=1&&wi,vi<=100;W>=1&&W<=10000)

輸入:

4

2 3

1 2

3 42 25

輸出:

7

分析:

首先確定兩個定義,

①該問題的階段是,物品種類為n,重量和價值分別為wi,vi。

②該問題的狀態是,物品裝還是不裝入背包。

因為當前階段的最優狀態和上一個狀態相關,而不管上一狀態是最優狀態還是最差狀態,如輸入輸出的栗子。

階段1)當n=4 ,W為0時,最大價值為0

  2) ,W為1時,w1<=W,若將其裝入背包則最大價值為v1即2,若不裝入則最大價值為0,取最大,裝入的情況,最大價值為2。

  3) ,W為2時,w0<=W,若將其裝入背包則最大價值為W-w0時的最大價值即階段1的最大價值0加v0為3,若不裝入則為階段2)的最大價值2。

  在階段3)發現,在階段3)時的狀態,不管階段1)階段2)是什麽狀態都是由階段1)和階段2)直接得到的。

每個階段的最優狀態可以從之前某個階段的某個或某些狀態直接得到,這個性質叫做最優子結構而不管之前這個狀態是如何得到的,這個性質叫做無後效性。所以才可以用動態規劃來做。

  寫動態規劃的一個方法(還有很多其他方法):將問題用記憶化搜索寫出偽代碼,推到處遞推公式,然後就可以根據遞推公式寫出動態規劃。

記憶化搜索的代碼:

#include <iostream>
#include <string.h>
#define MAX_N 101
using namespace std;
int n,W;
int w[MAX_N],v[MAX_N];
int dp[MAX_N][MAX_N];//記憶化數組
int rec(int i,int j){
    int res;
    //如果已經計算過,直接返回以前計算的值
    if(dp[i][j]>=0){
    return dp[i][j];
    }
    //臨界條件
    if(i==n){
        
//已經沒有剩余商品了 res=0; }//dp[n][j]=0 else if(j<w[i]){ res=rec(i+1,j); }//當j<w[i][j]時,dp[i][j]=dp[i+1][j]; else{ //裝和不裝兩種情況都試一下 res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); }//其他情況,dp[u][j]=max(dp(i+1,j),dp(i+1,j-w[i])+v[i]) //把每次計算過的值記錄下來 dp[i][j]=res; return res; } int main() { cin>>n; for(int i=0;i<n;i++){ cin>>w[i]>>v[i]; } cin>>W; //記憶化數組別忘了初始化 memset(dp,-1,sizeof(dp)); cout<<rec(0,W); return 0; }

根據記憶化數組得到遞推式:

dp[n][j]=0
當j<w[i][j]時,dp[i][j]=dp[i+1][j]
其他情況,dp[u][j]=max(dp(i+1,j),dp(i+1,j-w[i])+v[i])

動態規劃的代碼:

#include <iostream>

using namespace std;

int main()
{
    int n,W;
    int dp[101][101],w[101],v[101];
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>w[i]>>v[i];
    }
    cin>>W;
    for(int i=n-1;i>=0;i--){
        for(int j=0;j<=W;j++){
            if(j<w[i]){
                dp[i][j]=dp[i+1][j];
            }
            else{
                dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);
            }
        }
    }
    cout<<dp[0][W];
    return 0;
}

01背包問題