揹包問題總結

0-1揹包

1.採藥問題

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

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

輸入格式
第一行有22個整數T(1 \le T \le 1000)T(1≤T≤1000)和M(1 \le M \le 100)M(1≤M≤100),用一個空格隔開,TT代表總共能夠用來採藥的時間,MM代表山洞裏的草藥的數目。
接下來的MM行每行包括兩個在11到100100之間(包括11和100100)的整數,分別表示採摘某株草藥的時間和這株草藥的價值。

輸出格式
11個整數,表示在規定的時間內可以採到的草藥的最大總價值。

輸入輸出樣例
輸入

70 3
71 100
69 1
1 2

輸出

3

分析
普通的0-1揹包,求最大的收益

#include<iostream>
#include<cstdio>

using namespace std;
const int MAXN=1000;
int w[MAXN],v[MAXN];
int dp[MAXN];
int main(){
    int n,m;//容量爲n,物品有m個
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=m;i++){//上到下,關於第i個物品選與不選
        for(int j=n;j>=w[i];j--){//右到左,關於選與不選對應的收益
            dp[j]=max(dp[j-w[i]]+v[i],dp[j]);
        }
    }
    cout<<dp[n]<<endl;
}

2.開心的金明

題目描述
金明今天很開心,家裏購置的新房就要領鑰匙了,新房裏有一間他自己專用的很寬敞的房間。更讓他高興的是,媽媽昨天對他說:“你的房間需要購買哪些物品,怎麼佈置,你說了算,只要不超過N元錢就行”。今天一早金明就開始做預算,但是他想買的東西太多了,肯定會超過媽媽限定的N元。於是,他把每件物品規定了一個重要度,分爲5等:用整數1-5表示,第5等最重要。他還從因特網上查到了每件物品的價格(都是整數元)。他希望在不超過N元(可以等於N元)的前提下,使每件物品的價格與重要度的乘積的總和最大,請你幫助金明設計一個滿足要求的購物單。

輸入格式
第1行,爲2個正整數,用一個空格隔開:n m(其中N(<30000)表示總錢數,m(<25)爲希望購買物品的個數。)
從第2行到第m+1行,第j行給出了編號爲j−1的物品的基本數據,每行有22個非負整數 v p(其中v表示該物品的價格(v≤10000),p表示該物品的重要度(1−5)

輸出格式
1個正整數,爲不超過總錢數的物品的價格與重要度乘積的總和的最大值(<100000000)。

輸入輸出樣例
輸入

1000 5
800 2
400 5
300 5
400 3
200 2

輸出

3900

分析
普通的0-1揹包,但要注意w,v數組和dp數組開的大小與那些因素有關

#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=30;
int v[MAXN],w[MAXN];//v爲價格,w爲重要度,開的大小與物品數目有關
int dp[30005];//dp開的大小與揹包容量有關
int main(){
    int n,m;//n爲揹包容量,m爲物品數
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i]>>v[i];
        v[i]=v[i]*w[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//這裏的變化爲橫向,即揹包容量
            //cout<<dp[j]<<endl;
        }
    }
    cout<<dp[n]<<endl;
}

3.裝箱問題

題目描述
有一個箱子容量爲V(200000≤V≤20000),同時有n個物品(300<n≤30),每個物品有一個體積(正整數)。
要求n個物品中,任取若干個裝入箱內,使箱子的剩餘空間爲最小。

輸入格式
1個整數,表示箱子容量
1個整數,表示有n個物品
接下來n行,分別表示這n個物品的各自體積

輸出格式
1個整數,表示箱子剩餘空間。

輸入

24 6
8
3
12
7
9
7

輸出

0

分析
注意分析什麼是模板中的變量的轉換,比如什麼是揹包容量,什麼是價值,什麼是重量,這道題轉化爲求裝入若干個物品使其體積和最大

#include<iostream>

using namespace std;
const int MAXN=50;
int w[MAXN],v[MAXN];
int dp[30005];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i];
        v[i]=w[i];//這個題的物品重量等同於價值
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<n-dp[n]<<endl;
}

4.小A點菜

題目描述
不過uim由於買了一些書,口袋裏只剩M元(M≤10000)。餐館雖低端,但是菜品種類不少,有N種(N≤100),第i種賣ai元(ai ≤1000)。由於是很低端的餐館,所以每種菜只有一份。小A奉行“不把錢吃光不罷休”,所以他點單一定剛好吧uim身上所有錢花完。他想知道有多少種點菜方法。由於小A肚子太餓,所以最多隻能等待1秒。
輸入格式
第一行是兩個數字,表示N和M。第二行起N個正數ai(可以有相同的數字,每個數字均在1000以內)。
輸出格式
一個正整數,表示點菜方案數,保證答案的範圍在intint之內。
輸入

4 4
1 1 2 2

輸出

3

分析
這道題屬於0-1揹包中算方法種類的題,和普通的0-1揹包沒什麼大的區別,注意dp的初始化以及遞推公式就可以

#include<iostream>

using namespace std;
const int MAXN=101;
int w[MAXN];
int dp[10001]={1};//求方法種類的題初始化爲1
int main(){
    int n,m;
    cin>>m>>n;
    for(int i=1;i<=m;i++){
        cin>>w[i];
        for(int j=n;j>=w[i];j--){
            dp[j]=dp[j]+dp[j-w[i]];//選與不選兩種方案數的和
        }
    }
    cout<<dp[n]<<endl;
}

5.最大約數和

題目描述
選取和不超過S的若干個不同的正整數,使得所有數的約數(不含它本身)之和最大。

輸入格式
輸入一個正整數S。

輸出格式
輸出最大的約數之和。

輸入

11

輸出

9

說明/提示
取數字4和6,可以得到最大值(1+2)+(1+2+3)=9。

#include<iostream>

using namespace std;
const int MAXN=2000;
int v[MAXN];
int dp[10000];
int main(){
    int n,m;
    cin>>m;
    n=m;
    for(int i=1;i<=m;i++){
        for(int j=1;j<i;j++){
            if(i%j==0) v[i]+=j;
        }
        //cout<<i<<":"<<v[i]<<endl;
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=i;j--){
            dp[j]=max(dp[j],dp[j-i]+v[i]);//數字本身爲重量,數字的約數之和爲價值
        }
    }
    cout<<dp[n]<<endl;
}

6.精衛填海

題目描述
發鳩之山,其上多柘木。有鳥焉,其狀如烏,文首,白喙,赤足,名曰精衛,其名自詨。是炎帝之少女,名曰女娃。女娃遊於東海,溺而不返,故爲精衛。常銜西山之木石,以堙於東海。——《山海經》
精衛終於快把東海填平了!只剩下了最後的一小片區域了。同時,西山上的木石也已經不多了。精衛能把東海填平嗎?
事實上,東海未填平的區域還需要至少體積爲v的木石纔可以填平,而西山上的木石還剩下n塊,每塊的體積和把它銜到東海需要的體力分別爲k和m。精衛已經填海填了這麼長時間了,她也很累了,她還剩下的體力爲c。

輸入格式
輸入文件的第一行是三個整數:v、n、c。
從第二行到第n+1行分別爲每塊木石的體積和把它銜到東海需要的體力。

輸出格式
輸出文件只有一行,如果精衛能把東海填平,則輸出她把東海填平後剩下的最大的體力,否則輸出’Impossible’(不帶引號)。

輸入

100 2 10
50 5
50 5

輸出

0

輸入

10 2 1
50 5
10 2

輸出

Impossible

分析
這道題稍微有點不同,這道題是算揹包容量能剩下多少,所以在計算出各個dp後,要揹包容量從1開始遞增看是否能夠滿足,然後輸出剩下的最大體力.

#include<iostream>

using namespace std;
const int MAXN=10005;
int w[MAXN]; int v[MAXN];
int dp[20000];
int main(){
    int s,m,n;//s爲剩下沒填的體積,m爲物品件數,n爲揹包容量
    cin>>s>>m>>n;
    for(int i=1;i<=m;i++){
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=n;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//計算當前剩餘體力能搬的最大體積數
        }
    }
    for(int i=1;i<=n;i++){//遍歷各個揹包容量
        if(dp[i]>=s) {cout<<n-i<<endl; return 0;}
    }
    //if(dp[n]>=s) cout<<dp[n]-s<<endl; //錯誤,因爲在之前就可能已經滿足了,沒求到剩下的最大體力
    cout<<"Impossible"<<endl;
    return 0;
}

7.集合 Subset Sums

題目描述
對於從 1∼n 的連續整數集合,能劃分成兩個子集合,且保證每個集合的數字和是相等的。舉個例子,如果 n=3,對於{1,2,3} 能劃分成兩個子集合,每個子集合的所有數字和是相等的:{3} 和 {1,2} 是唯一一種分法(交換集合位置被認爲是同一種劃分方案,因此不會增加劃分方案總數)如果n=7,有四種方法能劃分集合 {1,2,3,4,5,6,7},每一種分法的子集合各數字和是相等的:

{1,6,7} 和 {2,3,4,5}
{2,5,7} 和 {1,3,4,6}
{3,4,7} 和 {1,2,5,6}
{1,2,4,7} 和 {3,5,6}

給出 n,你的程序應該輸出劃分方案總數。

輸入格式
輸入文件只有一行,且只有一個整數 n

輸出格式
輸出劃分方案總數。

輸入

7

輸出

4

分析
方法類的揹包問題,注意揹包容量是總的和的一半,而最終求的是劃分而不是集合,所以要除以2

#include<iostream>

using namespace std;
const int MAXN=100;
long long dp[1000]={1};//方法種類的揹包問題,初始化爲1
int main(){
    int m,n;
    cin>>m;
    n=m;//n爲物品數
    if((1+m)*m/2%2==0) m=(1+m)*m/2;//總的和,其一半是揹包的容量
    else{
        cout<<0; return 0;
    }
    for(int i=1;i<=n;i++){
        for(int j=m/2;j>=i;j--){
            dp[j]=dp[j]+dp[j-i];//選與不選
        }
    }
    cout<<dp[m/2]/2<<endl;//因爲求的是劃分數而不是和爲m/2的集合數
}

完全揹包

1.瘋狂的採藥

題目描述
LiYuxiang是個天資聰穎的孩子,他的夢想是成爲世界上最偉大的醫師。爲此,他想拜附近最有威望的醫師爲師。醫師爲了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裏對他說:“孩子,這個山洞裏有一些不同種類的草藥,採每一種都需要一些時間,每一種也有它自身的價值。我會給你一段時間,在這段時間裏,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”

如果你是LiYuxiang,你能完成這個任務嗎?
此題和原題的不同點:
1.每種草藥可以無限制地瘋狂採摘。
2.藥的種類眼花繚亂,採藥時間好長好長啊!師傅等得菊花都謝了!

輸入格式
輸入第一行有兩個整數T(1 <= T <= 100000)和M(1 <= M <= 10000),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裏的草藥的數目。接下來的M行每行包括兩個在1到10000之間(包括1和10000)的整數,分別表示採摘某種草藥的時間和這種草藥的價值。

輸出格式
輸出一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。

輸入

70 3
71 100
69 1
1 2

輸出

140

分析
完全揹包要修改j的遍歷方式,需要反着來

#include<iostream>

using namespace std;
const int MAXN=10005;
int w[MAXN],v[MAXN];
int dp[100005];

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=m;i++){
        for(int j=w[i];j<=n;j++){//完全揹包和0-1揹包這裏是反着的
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<dp[n]<<endl;
}

2.神奇的四次方數

題目描述
將一個整數m分解爲n個四次方數的和的形式,要求n最小。例如,m=706,706=54+34,則n=2。

輸入格式
一行,一個整數m。

輸出格式
一行,一個整數n。

輸入

706

輸出

2

分析
這道題因爲求的是最小,所以要將dp初始化爲一個比較大的數,並且dp[0]=0;
相當於給一個初始值,含義爲容量爲0時最少選擇0個數.

#include<iostream>
#include<cstring>
#include<math.h>
using namespace std;
const int MAXN=100;
int w[MAXN];//每個的w都是1
int dp[1000001];
int main(){
    //下面這兩步一定要寫
    memset(dp,63,sizeof(dp));
    //給了一個初始值,不然結果會是一個很大的數,其含義代表容量爲0時最少選擇0個數
    dp[0]=0;
    int n,m;//n爲揹包容量
    cin>>n;
    for(int i=1;pow(i,4)<=n;i++){
        w[i]=i*i*i*i;
        m=i;//m表示物品的數目
    }
    for(int i=1;i<=m;i++){
        for(int j=w[i];j<=n;j++){//v[i]是1
            dp[j]=min(dp[j],dp[j-w[i]]+1);
//            cout<<dp[j]<<endl;
        }
    }
    cout<<dp[n]<<endl;
}

分組揹包

1.通天之分組揹包

題目描述
自01揹包問世之後,小A對此深感興趣。一天,小A去遠遊,卻發現他的揹包不同於01揹包,他的物品大致可分爲k組,每組中的物品相互衝突,現在,他想知道最大的利用價值是多少。

輸入格式
兩個數m,n,表示一共有n件物品,總重量爲m
接下來n行,每行3個數ai,bi,ci,表示物品的重量,利用價值,所屬組數

輸出格式
一個數,最大的利用價值

輸入

45 3
10 10 1
10 5 1
50 400 2

輸出

10

分析
分組揹包採用結構體數組,其下標代表組數,內部的w,v數組存的是組內的元素情況,總組數要在輸入中算出,第二次遍歷也是先遍歷所有組,其本質相當於0-1揹包的問題
ps:但這道題5個測試點我只通過了三個,沒找到原因(╯°Д°)╯︵┻━┻

#include<iostream>

using namespace std;
const int MAXN=2000;

struct group{
    int group_size;//組的大小
    int w[MAXN],v[MAXN];
}G[1000];//下標代表組號
int dp[10000];
int main(){
    int n,m,s=0;//s代表組數
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        s=max(c,s);//統計總的組數
        G[c].group_size++;//c組內的成員增加
        int ci=G[c].group_size;
        G[c].w[ci]=a;
        G[c].v[ci]=a;
    }
    for(int i=1;i<=s;i++){//從上到下遍歷所有組
        for(int j=n;j>=0;j--){
            for(int k=1;k<=G[i].group_size;k++){//組內成員競爭,注意j不會變化
                if(j-G[i].w[k]>=0){//注意
                    dp[j]=max(dp[j],dp[j-G[i].w[k]]+G[i].v[k]);
                }
            }
        }
    }
    cout<<dp[n]<<endl;
}

2.金明的預算方案

題目描述
金明今天很開心,家裏購置的新房就要領鑰匙了,新房裏有一間金明自己專用的很寬敞的房間。更讓他高興的是,媽媽昨天對他說:“你的房間需要購買哪些物品,怎麼佈置,你說了算,只要不超過NN元錢就行”。今天一早,金明就開始做預算了,他把想買的物品分爲兩類:主件與附件,附件是從屬於某個主件的,下表就是一些主件與附件的例子:

主件 附件

電腦 打印機,掃描儀

書櫃 圖書

書桌 檯燈,文具

工作椅 無

如果要買歸類爲附件的物品,必須先買該附件所屬的主件。每個主件可以有0個、1個或2個附件。附件不再有從屬於自己的附件。金明想買的東西很多,肯定會超過媽媽限定的N元。於是,他把每件物品規定了一個重要度,分5等:用整數1−5表示,第5等最重要。他還從因特網上查到了每件物品的價格(都是10元的整數倍)。他希望在不超過N元(可以等於N元)的前提下,使每件物品的價格與重要度的乘積的總和最大。
請你幫助金明設計一個滿足要求的購物單。

輸入格式
第11行,爲兩個正整數,用一個空格隔開:

n m(其中N(<32000)表示總錢數,m(<60)爲希望購買物品的個數。) 從第2行到第m+1行,第j行給出了編號爲j−1的物品的基本數據,每行有3個非負整數

v p q(其中v表示該物品的價格(v<10000),p表示該物品的重要度(1-5),q表示該物品是主件還是附件。如果q=0,表示該物品爲主件,如果q>0,表示該物品爲附件,q是所屬主件的編號)

輸出格式
一個正整數,爲不超過總錢數的物品的價格與重要度乘積的總和的最大值(<200000)。

輸入

1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0

輸出

2200

分析
先轉化爲分組問題,假設有主(①),從(②),從(③)
所以得到一個組內四個相斥的成員:
①、①②、①③、①③

#include<iostream>

using namespace std;
const int MAXN=100;
struct group{
    int group_size;
    int w[MAXN],v[MAXN];
}G[2000];
int dp[32005];
//因爲每個主件可以有0個、1個或2個附件,所以對於組內只有四種相互排斥的情況,所以可以轉化爲分組揹包
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int v,p,q;
        cin>>v>>p>>q;
        if(q==0){//是這組的第一個,i就代表了組號
            G[i].group_size=1;
            G[i].w[1]=v;
            G[i].v[1]=p*v;
        }
        else{
            if(G[q].group_size==1){//一主一從,q代表所從屬的組
                G[q].group_size=2;
                G[q].w[2]=G[q].w[1]+v;//一個主,從一號
                G[q].v[2]=G[q].v[1]+p*v;
            }
            else{
                G[q].group_size=4;//
                G[q].w[3]=G[q].w[1]+v;//一個主,從二號
                G[q].v[3]=G[q].v[1]+p*v;
                G[q].w[4]=G[q].w[2]+v;//一個主,從一號,從二號
                G[q].v[4]=G[q].v[2]+p*v;
            }
        }
    }
    for(int i=1;i<=m;i++){//遍歷所有組
        for(int j=n;j>=0;j--){
            for(int k=1;k<=G[i].group_size;k++){//組內成員競爭
                if(j>=G[i].w[k]){//
                    dp[j]=max(dp[j],dp[j-G[i].w[k]]+G[i].v[k]);
                    //cout<<dp[j]<<endl;
                }
            }
        }
    }
    cout<<dp[n]<<endl;

}

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