dp-揹包問題

揹包問題理論模板

一個揹包總容量爲V, 如今有N個物品, 第i個物品容量爲weight[i], 價值爲value[i], 如今往揹包裏面裝東西, 怎樣裝才能使揹包內物品總價值最大.主要分爲3類:ios

  • 01揹包, 每一個物品只能取0個,或者1個.
  • 徹底揹包, 每一個物品能夠取無限次.
  • 多重揹包, 每種物品都有個數限制, 第i個物品最多能夠爲num[i]個.

整體的,又分爲揹包恰好裝滿,與揹包沒有裝滿兩種狀況less

01揹包問題

每種物品都只有1個,只有選擇與不選擇兩種狀態
有N件物品和一個容量爲V的揹包,每種物品只有一件,能夠選擇放或不放。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入揹包可以使這些物品的重量總和不超過揹包容量,且價值總和最大。ide

對於任何只存在兩種狀態的問題均可以轉化爲01揹包問題測試

定義狀態dp[i][v]表示前i件物品恰放入一個容量爲v的揹包能夠得到的最大價值。
狀態轉移方程:
dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+v[i])優化

`dp[v]=max(dp[v],dp[v-w[i]]+v[i])`

空間複雜度O(NW)或者O(W),時間複雜度爲O(nW)this

for(int i=0;i<n;i++){
    for(int j=揹包大小;j>=w[i];j--){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}

分析:
首先,大循環從0到n是指n個物體
其次,內循環是從揹包大小到當前物體的重量,是爲了減小時間複雜度而這麼弄的
對於w[i]是物體的重量,若是剩餘揹包的大小小於w[i]了,就無需再循環,因此一個端點在w[i]
而j的含義是當前的揹包總容量,而容量不可能大於揹包大小,因此另外一個大小就在揹包大小
若是是升序,那麼就會對dp[j-w[i]]進行操做,也就意味着,若是j是w[i]的倍數,那麼v[i]就會不斷地加進去,也就是徹底揹包問題了
而對於降序的話,也就保證了每次第j個物品都只能被放入1此,而不是屢次,就是01揹包了idea

多重揹包問題

每種物品最多有n件可用
有N種物品和一個容量爲V的揹包。第i種物品最多有n件可用,每件體積是c,價值是w。求解將哪些物品裝入揹包可以使這些物品的體積總和不超過揹包容量,且價值總和最大。spa

對於多重揹包問題,能夠轉化爲01揹包問題,好比2個價值爲5,重量爲2的物品,能夠轉化爲a和b兩個物品,一個價值爲5,重量爲2,一個價值也爲5,重量也爲2code

狀態轉移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) 其中0<=k<=c[i]ip

`dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i])`

k是每種物品放的數量

for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
        for(int k=1;k<=c[i];k++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
}

徹底揹包問題

每種物品都有無限件
有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的體積是c,價值是w。將哪些物品裝入揹包可以使這些物品的體積總和不超過揹包容量,且價值總和最大。

狀態轉移方程:
dp[i][j]=max(dp[i][j],dp[i-1][v-k*w[i]]+k*v[i]) 其中0<=k*w[i]<=揹包大小

`dp[j] = max(dp[j], dp[j - w[i]] + v[i]);`

分析見01揹包物體,和01揹包同樣,就是內循環的循環方向不一樣而已,一個升,一個降

空間雜度爲O(NW)或O(W),時間複雜度爲O(NW)

代碼

for (int i = 1; i <= n; i++) {
    for (int j = w[i]; j <= 揹包大小; j++) {
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    }
}

揹包混用

將3種揹包進行混用,若是對於有些物品最多隻能選一次,有些能夠無限選
則利用01揹包和徹底揹包的一行代碼不一樣,進行判斷

for(int i=0;i<n;i++){
    if(第i個物品是01揹包){
        for(int j=揹包大小;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }else if(第i個物品是徹底揹包){
        for (int j = w[i]; j <= 揹包大小; j++) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
}

若是再加上多重揹包的話
揹包混用僞代碼

for i= 1 to n
    if 第i件物品屬於01揹包
        ZeroOnePack(dp,wi,vi)
    else if 第i件物品屬於徹底揹包
        CompletePack(dp,wi,vi)
    else if 第i件物品屬於多重揹包
        MultiplePack(dp,wi,vi,ni)

總結模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=1000000;
int v[N],w[N];
int dp[N];
void ZeroOnePack(int i){
    for(int j=揹包大小;j>=w[i];j--){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}
void CompletePack(int i){
    for(int j=w[i];j<=揹包大小;j++){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}
void MultiplePack(int i){
    for(int j=m;j>=0;j--)
        for(int k=1;k<=c[i];k++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
}
int main(){
    memste(dp,0,sizeof(dp));
    for(int i=0;i<n;i++){
    if(第i個物品是01揹包問題){
        ZeroOnePack(i);
    }else if(第i個物品是徹底揹包問題){
        CompletePack(i);
    }else if(第i個物品是多重揹包問題){
        MultiplePack(i);
    }
}

作題記錄

第一題:

徹底揹包問題
HUD1248

Problem Description
不死族的巫妖王發工資拉,死亡騎士拿到一張N元的鈔票(記住,只有一張鈔票),爲了防止本身在戰鬥中頻繁的死掉,他決定給本身買一些道具,因而他來到了地精商店前.

死亡騎士:"我要買道具!"

地精商人:"咱們這裏有三種道具,血瓶150塊一個,魔法藥200塊一個,無敵藥水350塊一個."

死亡騎士:"好的,給我一個血瓶."

說完他掏出那張N元的大鈔遞給地精商人.

地精商人:"我忘了提醒你了,咱們這裏沒有找客人錢的習慣的,多的錢咱們都當小費收了的,嘿嘿."

死亡騎士:"......"

死亡騎士想,與其把錢當小費送個他還不如本身多買一點道具,反正之後都要買的,早點買了放在家裏也好,可是要儘可能少讓他賺小費.

如今死亡騎士但願你能幫他計算一下,最少他要給地精商人多少小費.

Input
輸入數據的第一行是一個整數T(1<=T<=100),表明測試數據的數量.而後是T行測試數據,每一個測試數據只包含一個正整數N(1<=N<=10000),N表明死亡騎士手中鈔票的面值.

注意:地精商店只有題中描述的三種道具.

Output
對於每組測試數據,請你輸出死亡騎士最少要浪費多少錢給地精商人做爲小費.

Sample Input

2
900
250

Sample Output

0
50

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int w[]={150,200,350};
int v[]={150,200,350};
int dp[10005];
int main(){
    int t;
    cin>>t;
    while(t--){
        int n;
        scanf("%d",&n);
        memset(dp,0,sizeof(dp));

        for(int i=0;i<3;i++){
            for(int j=w[i];j<=n;j++){
                dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
        printf("%d\n",n-dp[n]);
    }

    return 0;
}

第二題:

徹底揹包問題
Problem Description:
Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible, the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.

But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!
Input
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units, W is it's weight in grams.
Output
Print exactly one line of output for each test case. The line must contain the sentence "The minimum amount of money in the piggy-bank is X." where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly, print a line "This is impossible.".
Sample Input

3
10 110
2
1 1
30 50

10 110
2
1 1
50 30

1 6
2
10 3
20 4

Sample Output

The minimum amount of money in the piggy-bank is 60.
The minimum amount of money in the piggy-bank is 100.
This is impossible.

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const long long INF=500005,N=10005;
int w[N];
int v[N];
int dp[N];
int main(){
    int t;
    cin>>t;
    while(t--){
        int e,f;
        scanf("%d%d",&e,&f);
        int n;
        cin>>n;
        int W=f-e;
        for(int i=0;i<n;i++){
            scanf("%d%d",&v[i],&w[i]);
        }
        //求最小值,因此對全部的dp進行賦值大數字,但除了第一個數,由於對全部賦值的話,最小值就是大數
        for(int i=1;i<=W;i++){
            dp[i]=INF;
        }

        for(int i=0;i<n;i++){
            for(int j=w[i];j<=W;j++){
                dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
            }
        }

        if(dp[W]==INF){
            printf("This is impossible.\n");
        }else{
            printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);
        }

    }

    return 0;
}

第三題:

機率dp的01揹包問題
HDU1203
Problem Description
Speakless很早就想出國,如今他已經考完了全部須要的考試,準備了全部要準備的材料,因而,便須要去申請學校了。要申請國外的任何大學,你都要交納必定的申請費用,這但是很驚人的。Speakless沒有多少錢,總共只攢了n萬美圓。他將在m個學校中選擇若干的(固然要在他的經濟承受範圍內)。每一個學校都有不一樣的申請費用a(萬美圓),而且Speakless估計了他獲得這個學校offer的可能性b。不一樣學校之間是否獲得offer不會互相影響。「I NEED A OFFER」,他大叫一聲。幫幫這個可憐的人吧,幫助他計算一下,他能夠收到至少一份offer的最大機率。(若是Speakless選擇了多個學校,獲得任意一個學校的offer均可以)。

Input
輸入有若干組數據,每組數據的第一行有兩個正整數n,m(0<=n<=10000,0<=m<=10000)
後面的m行,每行都有兩個數據ai(整型),bi(實型)分別表示第i個學校的申請費用和可能拿到offer的機率。
輸入的最後有兩個0。

Output
每組數據都對應一個輸出,表示Speakless可能獲得至少一份offer的最大機率。用百分數表示,精確到小數點後一位。

Sample Input

10 3
4 0.1
4 0.2
5 0.3
0 0

Sample Output

44.0%

Hint

You should use printf("%%") to print a '%'.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100000+5;
int v[N];
float p[N],dp[N];
int main(){
    int n,m;//揹包大小和學校數目
    while(scanf("%d%d",&n,&m),(n+m)!=0){
        for(int i=0;i<m;i++){
            scanf("%d%f",&v[i],&p[i]);
        }

        memset(dp,0,sizeof(dp));

        for(int i=0;i<m;i++){
            for(int j=n;j>=v[i];j--){
                dp[j]=max(dp[j],1-(1-dp[j-v[i]])*(1-p[i]));
            }
        }


        printf("%.1f%%\n",dp[n]*100);

    }
    return 0;
}

第四題: 多重揹包問題

多重揹包問題HDU2191,此方法時間複雜度爲O(NCV)

Problem Description
如今假設你一共有資金n元,而市場有m種大米,每種大米都是袋裝產品,其價格不等,而且只能整袋購買。
請問:你用有限的資金最多能採購多少公斤糧食呢?

Input
輸入數據首先包含一個正整數C,表示有C組測試用例,每組測試用例的第一行是兩個整數n和m(1<=n<=100, 1<=m<=100),分別表示經費的金額和大米的種類,而後是m行數據,每行包含3個數p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分別表示每袋的價格、每袋的重量以及對應種類大米的袋數。

Output
對於每組測試數據,請輸出可以購買大米的最多重量,你能夠假設經費買不光全部的大米,而且經費你能夠不用完。每一個實例的輸出佔一行。

Sample Input
1
8 2
2 100 4
4 100 2

Sample Output
400

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
    int t;
    cin>>t;
    while(t--){

        int n,m;
        scanf("%d%d",&n,&m);

        int w[105],v[105],c[105];
        int dp[205];

        memset(dp,0,sizeof(dp));

        for(int i=0;i<m;i++){
            scanf("%d%d%d",&v[i],&w[i],&c[i]);
        }

        for(int i=0;i<m;i++){
            for(int k=1;k<=c[i];k++){
                for(int j=n;j>=v[i];j--){
                   dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
                }
            }
        }
        printf("%d\n",dp[n]);

    }


    return 0;
}

優化

二進制優化時間不會。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int w[105],v[105],c[105];
int dp[205];//dp[i]指的是購買價值爲i的大米時的總重量

//v爲當前的價值,w爲當前的重量
void zeroonebag(int v,int w){
    for(int j=n;j>=v;j--){
        dp[j]=max(dp[j],dp[j-v]+w);
    }
}
void completebag(int v,int w){
    for(int j=v;j<=n;j++){
        dp[j]=max(dp[j],dp[j-v]+w);
    }
}
void multiple(int v,int w,int c){
    //價值大於擁有的價值,則是徹底揹包問題
    if(c*v>=n){
        completebag(v,w);
        return;
    }
    
    //01揹包
    int k=1;

    while(k<=c){
        zeroonebag(k*v,k*w);
        c=c-k;
        k=k*2;
    }

    zeroonebag(c*v,c*w);

}
int main(){
    int t;
    cin>>t;
    while(t--){


        scanf("%d%d",&n,&m);

        memset(dp,0,sizeof(dp));

        for(int i=0;i<m;i++){
            scanf("%d%d%d",&v[i],&w[i],&c[i]);
        }


        for(int i=0;i<m;i++){
            multiple(v[i],w[i],c[i]);
        }

        printf("%d\n",dp[n]);


    }

    return 0;
}
相關文章
相關標籤/搜索