面試常見的01揹包、多重揹包、完全揹包

一個不錯的《揹包九講》的賞析博客。https://blog.csdn.net/yandaoqiusheng/article/details/84782655#commentBox

一、01揹包

 HihoCoder - 1038 

https://vjudge.net/problem/HihoCoder-1038

題目

且說上一週的故事裏,小Hi和小Ho費勁心思終於拿到了茫茫多的獎券!而現在,終於到了小Ho領取獎勵的時刻了!

小Ho現在手上有M張獎券,而獎品區有N件獎品,分別標號爲1到N,其中第i件獎品需要need(i)張獎券進行兌換,同時也只能兌換一次,爲了使得辛苦得到的獎券不白白浪費,小Ho給每件獎品都評了分,其中第i件獎品的評分值爲value(i),表示他對這件獎品的喜好值。現在他想知道,憑藉他手上的這些獎券,可以換到哪些獎品,使得這些獎品的喜好值之和能夠最大。

分析

抽象:

 揹包容量 m     1e5

 石頭個數 n     500

 石頭的重量爲 need(i) 2e5

 價值爲    value(i) 1e3

設dp[i][j]表示第i個石頭(前i個石頭)、揹包空間剩餘j時可達到的最大收益。

有方程:

dp[i,j]=     max(   放:dp[i-1,j-w[i]] + v[i]    (條件爲wi<=j),

                          不放:dp[i-1,j]

                        );

1<=i<=n

1<=j<=m

觀察方程,與i無關,且每個方程需要得知對應j較小的答案。故空間優化:

dp[j]=     max(   放:dp[j-w[i]] + v[i]    (條件爲wi<=j),

                          不放:dp[j]

                        );

1<=i<=n

m>=j>=1 (倒序)

ac代碼

#include <iostream>
#include <stdlib.h>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int d[maxn];
/*
 揹包容量 m     1e5
 石頭個數 n     500
 石頭的重量爲 need(i) 2e5
 價值爲    value(i) 1e3
 
 */
int main(int argc, const char * argv[]) {
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d%d",w+i,v+i);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=1 && w[i]<=j;j--){
            d[j]=max(d[j-w[i]]+v[i],d[j]);
          
        }
    }
    cout<<d[m]<<endl;
    return 0;
}

二、多重揹包

題目

A - 悼念512汶川大地震遇難同胞――珍惜現在,感恩生活

 HDU - 2191 

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

分析

混合揹包(多重揹包問題),可直接轉化爲01揹包問題,(也可用二進制優化)。

當然,還有O(vn)的方法,需要用到單調隊列。具體見揹包九講。此處不列舉了。

#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[200];
int c[maxn];
/*
 揹包容量 n     100
 種類個數 m     100
 石頭個數cnt    2000
重量w 20 價值v 200 個數c 20
 */
int cnt;
inline void func(int x,int y,int z){
    int wei=1;
    while(z){
        if(wei>z){
            wei=z;
        }
        w[++cnt]=wei*x;
        v[cnt]=wei*y;
        z-=wei;
        wei=wei<<1;
    }
}
int main(int argc, const char * argv[]) {
    int n,m;
    int T;
    cin>>T;
    int x,y,z;
    while(T--){
        cnt=0;
        cin>>n>>m;
        memset(dp, 0,sizeof(dp));
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            func(x,y,z);
        }
        for(int i=1;i<=cnt;i++){
            for(int j=n;j>=1 && j>=w[i];j--){
                dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
            }
        }
        cout<<dp[n]<<endl;
        
    }
   
    return 0;
}

三、完全揹包

問題:

完全揹包

 HihoCoder - 1043 

有n種不同種類的硬幣,每種都有無限個。每種硬幣的價值不同,重量不同。現在你有一個袋子最多可裝m的重量,求在滿足重量要求的情況下得到的最大價值是多少。

解析

完全揹包問題。相比於01揹包比較“取”和“不取”,完全揹包需要比較"取多少",所以需要多一層循環。

設重量爲wi ,價值爲vi

基本方程爲:

定義:dp[i,j]表示前i種硬幣、袋子剩餘容量爲j時,可以得到的最大價值。

方程:

dp[i,j]=max(     dp[i-1,j-k*w[i]]+v[i]*k    ) ,  0<=k<inf  &&  w[i]*k<=j

1<=i<=n

1<=j<=m 

空間壓縮後:

dp[j]=max(     dp[j-k*w[i]]+v[i]*k    ) ,  0<=k<inf  &&  w[i]*k<=j

1<=i<=n

m>=j>=w[i]

AC代碼:

//
//  main.cpp
//  test
//
//  Created by dawn on 2019/8/26.
//  Copyright © 2019 chuyi. All rights reserved.
//

#include <iostream>
#include <stdlib.h>
#include <cstring>
using namespace std;
int const maxn=1e5+10;
#define max(i,j) ((i)>(j)?(i):(j))
int v[maxn];
int w[maxn];
int dp[maxn];
/*
 總容量 m 石頭種類數n
 價值v
 重量w
 */

int main(int argc, const char * argv[]) {
    int n,m;
    cin>>n>>m;
    memset(dp, 0,sizeof(dp));
    for(int i=1;i<=n;i++){
        scanf("%d%d",w+i,v+i);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=w[i];j--){
            int max_k = j/w[i];
            for(int k=0;k<=max_k;k++){
                dp[j]=max(
                          dp[j],
                          dp[j-k*w[i]]+v[i]*k
                          );
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

優化?

 

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