【dp】 揹包問題

問題一:01揹包

題目:html

【題目描述】

一個旅行者有一個最多能裝 M 公斤的揹包,如今有 n件物品,它們的重量分別是W1W2...,Wn它們的價值分別爲C1,C2,...,Cn求旅行者能得到最大總價值。ios

【輸入】

第一行:兩個整數,MM(揹包容量,M200)和NN(物品數量,N30);數組

2..N+12..N+1行:每行二個整數WiCiWi,Ci,表示每一個物品的重量和價值。優化

【輸出】

僅一行,一個數,表示最大總價值。atom

【輸入樣例】

10 4
2 1
3 3
4 5
7 9

【輸出樣例】

12spa

這類問題就是01揹包問題,是dp揹包中的最簡單的一種code

狀態轉移方程:f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i])xml

即f[i][v]表示前i件物品恰放入一個容量爲v的揹包能夠得到的最大價值。htm

將前i件物品放入容量爲v的揹包中」這個子問題,若只考慮第i件物品的策略(放或不放),那麼就能夠轉化爲一個只牽扯前i-1件物品的問題。若是不放第i件物品,那麼問題就轉化爲「前i-1件物品放入容量爲v的揹包中」,價值爲f[i-1][v];若是放第i件物品,那麼問題就轉化爲「前i-1件物品放入剩下的容量爲v-c[i]的揹包中」,此時能得到的最大價值就是f[i-1][v-c[i]]再加上經過放入第i件物品得到的價值w[i](來自百度)blog

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int f[1005][1050],c[1050],w[1050],m,n;
int main() {
    cin>>m>>n;
    for(int i=1; i<=n; ++i) {
        cin>>w[i]>>c[i];
    }
    for(int i=1; i<=n; ++i) {
        for(int v=m; v>0; v--) {
            if(w[i]<=v) {
                f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
            } else {
                f[i][v]=f[i-1][v];
            }
        }
    }
    cout<<f[n][m];
    return 0;

}

 

問題來了開一個二維數組是否是有點浪費空間(想一想蛇形填數那個題,二維數組開不到那麼大,就GG了)因此只須要用f[v]表示重量不超過v的最大價值就OK了

 f[i]=max(f[v],f[v-c[i]]+w[i])

爲何能夠從二維變成一維的呢?

由於f[i][v]是從f[i-1][v],f[i-1][v-c[i]]+w[i])得出的,當我將v從m開始向前枚舉時,每次都會更新當前的第i個物體的更新,只依賴於第i-1個的物體的結果因此能夠用滾動數組,每次只存i和i-1時候的值  第i個物體在容積爲j狀態的更新,只依賴i-1物體容量裏j-w[i]的狀態的結果因此,從後面開始向前更新,則求j位置時候,j-w[i]的值依舊爲i-1時候的值

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int f[1050],c[1050],w[1050],m,n;
int main() {
    cin>>m>>n;
    for(int i=1; i<=n; ++i) {
        cin>>w[i]>>c[i];
    }
    for(int i=1; i<=n; ++i) {
        for(int v=m; v>0; v--) {
            if(w[i]<=v) {
                f[v]=max(f[v],f[v-w[i]]+c[i]);
            } else {
                f[v]=f[v];
            }
        }
    }
    cout<<f[m];
    return 0;

}

01揹包爲何枚舉v時要逆序?

爲了不要使用的子狀態收到影響。(很重要!)

問題二:徹底揹包

【題目描述】

設有n種物品,每種物品有一個重量及一個價值。但每種物品的數量是無限的,同時有一個揹包,最大載重量爲M,今從n種物品中選取若干件(同一種物品能夠屢次選取),使其重量的和小於等於M,而價值的和爲最大。

【輸入】

第一行:兩個整數,M(揹包容量,M≤200)和N(物品數量,N≤30);

第2..N+1行:每行二個整數Wi,Ci,表示每一個物品的重量和價值。

【輸出】

僅一行,一個數,表示最大總價值。

【輸入樣例】

10 4
2 1
3 3
4 5
7 9

【輸出樣例】

max=12


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

 [思路]:這個問題很是相似於01揹包,所不一樣的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關的策略已並不是取或不取兩種,而是有取0件、取1件、取2件……。仍然按照解01揹包時的思路,令f[i][v]表示前i種物品恰放入一個容量爲v的揹包的最大權值。仍然能夠按照每種物品不一樣的策略寫出狀態轉移方程:

f[i][j] = max{f[i][j],f[i-1][j - k * c[i]] + k * w[i]}(0<=k*c[i]<=v)

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int w[1005],c[1005],f[1005][1005],m,n;
int main() {
    cin>>m>>n;
    for(int i=1; i<=n; ++i) {
        cin>>w[i]>>c[i];
    }
    for(int i=1; i<=n; ++i) {
        for(int v=1; v<=m; ++v) {
            if(v<w[i]) {
                f[i][v]=f[i-1][v];
            } else {
                if(f[i-1][v]>f[i][v-w[i]]+c[i]) {
                    f[i][v]=f[i-1][v];
                } else {
                    f[i][v]=f[i][v-w[i]]+c[i];
                }
            }
        }
    }
    cout<<"max="<<f[n][m];
    return 0;
}

 

 

同上,和01揹包同樣開二維數組浪費空間

注意v要從前日後枚舉,爲何?

 在01揹包問題裏面,咱們逆序遍歷V是爲了保證f[i]始終是i-1物品推出的,從而保證每種物品只用一次。而徹底揹包問題裏面咱們就能夠正序遍歷,這樣就能夠在一次遍歷f[V]中考慮第i種物品的全部拿法。至於爲何這樣就能求出全部的,我會在明天作一個解釋

解釋開始:由於第i種物品一旦出現,原來沒有第i種物品的狀況下可能有一個最優解,如今第i種物品 出現了,而它的加入有可能獲得更優解,因此以前的狀態須要進行改變,故須要正序。

直接用一組例子來解釋:
樣例:

10 4
2 1
3 3
4 5
7 9

運行過程f數組內:

0

0  0 

0  0  0 

0  0  1  0

0  0  1  1  0 

0  0  1  1  2  0

0  0  1  1  2  2  0

0  0  1  1  2  2  3  0

0  0  1  1  2  2  3  3  0

0  0  1  1  2  2  3  3  4  0

0  0  1  1  2  2  3  3  4  4  0

0  0  1  1  2  2  3  3  4  4  5  0

0  0  1  3  2  2  3  3  4  4  5  0

0  0  1  3  3  2  3  3  4  4  5  0

0  0  1  3  3  4  3  3  4  4  5  0

0  0  1  3  3  4  6  3  4  4  5  0

0  0  1  3  3  4  6  6  4  4  5  0

0  0  1  3  3  4  6  6  7  4  5  0

0  0  1  3  3  4  6  6  7  9  5  0

0  0  1  3  3  4  6  6  7  9  9  0

0  0  1  3  5  4  6  6  7  9  9  0

0  0  1  3  5  5  6  6  7  9  9  0

0  0  1  3  5  5  6  8  7  9  9  0

0  0  1  3  5  5  6  8  10  9  9  0

0  0  1  3  5  5  6  8  10  10  9  0

0  0  1  3  5  5  6  8  10  10  11  0

0  0  1  3  5  5  6  8  10  10  12  0

liuzitong大佬的解釋:

揹包容量爲5

有1個物品,體積爲2,價值爲3(這麼簡單的例子不是由於我懶)

從5開始呢?

f[5]=f[5-2]+3;

f[4]=f[4-2]+3;

f[3]=f[3-2]+3;

f[2]=f[2-2]+3;

1和0不行

能夠看出,從5到2,關係分別是

由3推5

由2推4

由1推3

由0推2

從一開始推出來的5開始沒有被用過的再被利用

從1開始呢?

1和0不行

f[2]=f[2-2]+3;

f[3]=f[3-2]+3;

f[4]=f[4-2]+3;

f[5]=f[5-2]+3;

能夠看出,從5到2,關係分別是

由0推2

由1推3

由2推4

由3推5

能夠看出:

由0推出的2又在推4的時候用到了,這就實現了徹底揹包

 

最終答案就是12(wc寫死我了,好多啊)

若有bug請評論指明。

優化代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int w[1005],c[1005],f[1005],m,n;
int main() {
    cin>>m>>n;
    for(int i=1; i<=n; ++i) {
        cin>>w[i]>>c[i];
    }
    for(int i=1; i<=n; ++i) {
        for(int v=w[i]; v<=m; ++v) {//注意v從w[i]開始枚舉也可從1開始枚舉
            if(f[v-w[i]]+c[i]>f[v])
                f[v]=f[v-w[i]]+c[i];
        }
    }
    cout<<"max="<<f[m];
    return 0;
}

 

問題三:多重揹包

【題目描述】

爲了慶賀班級在校運動會上取得全校第一名成績,班主任決定開一場慶功會,爲此撥款購買獎品犒勞運動員。指望撥款金額能購買最大價值的獎品,能夠補充他們的精力和體力。

【輸入】

第一行二個數n(n≤500),m(m≤6000),其中n表明但願購買的獎品的種數,m表示撥款金額。

接下來n行,每行3個數,v、w、s,分別表示第I種獎品的價格、價值(價格與價值是不一樣的概念)和能購買的最大數量(買0件到s件都可),其中v≤100,w≤1000,s≤10。

【輸出】

一行:一個數,表示這次購買能得到的最大的價值(注意!不是價格)。

【輸入樣例】

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

 

【輸出樣例】

1040

 

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

轉化爲01揹包問題:

樸素方法:把第i種物品換成n[i]件01揹包中的物品,則獲得了物品數爲Σn[i]的01揹包問題,直接求解,複雜度是O(V*Σn[i])。 Σ表示把n[i]加起來,就是說兩層循環的複雜度

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int v[6666],w[6666],s[6666],f[6666],n,m;
int main() {
    cin>>n>>m;
    for(int i=1; i<=n; ++i) {
        cin>>v[i]>>w[i]>>s[i];
    }
    for(int i=1; i<=n; ++i) {//枚舉i
        for(int j=m; j>=0; j--) {//枚舉空間(容量)
            for(int k=1; k<=s[i]; ++k) {//枚舉選的數量
                if(j-k*v[i]<0)
                    break;//裝不下了就退出
                else {
                    f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
                }
            }
        }
    }
    cout<<f[m];
    return 0;
}

 優化版:

利用二進制拆分。複雜度O(VΣlog n[i])

考慮二進制的思想,咱們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換之後的物品。另外,取超過n[i]件的策略必不能出現。方法是:將第i種物品分紅若干件物品,其中每件物品有一個係數,這件物品的費用和價值均是原來的費用和價值乘以這個係數。使這些係數分別爲1,2,4,...,2^(k-1),n[i]-2^k+1,且k是知足n[i]-2^k+1>0的最大整數。(來自一本通)

拆分過程:

cin>>x>>y>>z;
        while(z>=sum) { //sum是指數
            v[++len]=x*sum;
            w[len]=y*sum;
            z-=sum;
            sum*=2;
        }
        v[++len]=x*z;
        w[len]=y*z;

 

 完整代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int v[6666],w[6666],s[6666],f[6666],n,m;
int main() {
    cin>>n>>m;
    int len=0;
    for(int i=1; i<=n; ++i) {
        int x,y,z;
        int sum=1;
        /*x是價格,y是價值,z是數量*/
        cin>>x>>y>>z;
        while(z>=sum) { //sum是指數
            v[++len]=x*sum;
            w[len]=y*sum;
            z-=sum;
            sum*=2;
        }
        v[++len]=x*z;
        w[len]=y*z;
    }
    for(int i=1; i<=len; ++i) {
        for(int j=m; j>=v[i]; j--) {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m];
    return 0;
}

 

問題四:分組揹包

問題

有N件物品和一個容量爲V的揹包。第i件物品的費用是c[i],價值是w[i]。這些物品被劃分爲若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入揹包可以使這些物品的費用總和不超過揹包容量,且價值總和最大。

思路

這個問題變成了每組物品有若干種策略:是選擇本組的某一件,仍是一件都不選。也就是說設f[v]表示前k組物品花費費用v能取得的最大價值

for 全部的組k
    for v=V..0
        for 全部的i屬於組k
      f[v]=max{f[v],f[v-w[i]]+c[i]}

注意這裏的三層循環的順序,「for v=V..0」這一層循環必須在「for 全部的i屬於組k」以外。這樣才能保證每一組內的物品最多隻有一個會被添加到揹包中。

題目

P1757 通天之分組揹包

代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#define ll long long int
#define MAXN 1005
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int a[MAXN][MAXN],f[MAXN],c[MAXN],w[MAXN],sum[MAXN];
int main()
{
    int n,m;
    cin>>m>>n;
    int zu=0;
    for(int i=1;i<=n;++i)
    {
        int p;
        cin>>w[i]>>c[i]>>p;
        sum[p]++;
        zu=max(zu,p);
        a[p][sum[p]]=i;
    }
    for(int i=1;i<=zu;++i)
    {
        for(int j=m;j>=0;--j)
        {
            for(int k=1;k<=sum[i];++k)
            {
                if(j>=w[a[i][k]])
                f[j]=max(f[j],f[j-w[a[i][k]]]+c[a[i][k]]);
            }
        }
    }
//    cout<<f[m];
    int ans=f[m];
    cout<<ans;//ac
    return 0;
}

問題五:有依賴的揹包問題

例題:

 

思路:

這類問題是01揹包的變形。全部的物品分爲兩類,一類是主件,另外一類是附件,每個附件都有它的主件,選取它的主件以後才能選取附件。

詳見:個人另外一篇博客

相關文章
相關標籤/搜索