動態規劃_揹包問題筆記

http://www.javashuo.com/article/p-zqreiczo-ex.htmlphp

dp自從知道有這麼個東西時,就沒有好好的學,,如今一看道dp的題就繞道走,,,可是,不少比賽中的dp問題有不少,,別人都會,本身不會很吃虧啊,,,因而從基礎開始一點一點的補inghtml

揹包問題

揹包問題是動態規劃的經典也是基礎,,,下面的東西部分來自 揹包九講ios

01揹包

01揹包指的是對於任意的物品只有 取或不取 兩種狀態,,c++

狀態轉移方程

狀態轉移方程爲:數組

\(F[i,j]=max(F[i-1,j], F[i-1,j-c_i]+w_i)\)優化

外層循環枚舉物品總數:\(for \ i=1\ to\ n\)ui

內層循環枚舉揹包的容量: \(for \ j=c_i \ to \ v\)spa


空間優化後的狀態轉移方程:.net

\(F[j]=max(F[j], F[j-c_i]+w_i)\)code

外層循環不變,內層循環變爲: \(for \ j=v \ to \ c_i\)

外層循環能夠繼續優化爲: \(for \ j \ to \ max(v-\sum_i^nw_i, \ \ c_i)\)

初始化

  • 剛好裝滿揹包:\(F[0]=0,F[1..v]=-\infty\)
  • 沒必要裝滿: \(F[0..v]=0\)

初始化F數組就是在沒有任何物品能夠放入揹包時的合法狀態,因此,前者只有容量爲零的揹包什麼都不裝的狀況下是剛好裝滿的,其餘容量的揹包都是未定義的狀態,無合法解;後者由於沒必要裝滿,因此什麼都不裝的時候就是一個合法解,這時的價值爲零。

例題

hud-2602

裸的01揹包,,直接作,,,注意判斷當前物品是否能放入揹包,,再選擇放與不放,,

還有內層循環容量的遍歷是從0開始

memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; ++i)
            for(int j = 0; j <= v; ++j)
                if(c[i] <= j)//能放入時,選擇放與不放
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]);
                else
                    dp[i][j] = dp[i - 1][j];
        printf("%d\n", dp[n][v]);

空間優化後的方法:

memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; ++i)
            for(int j = v; j >= 0; --j)
                if(c[i] <= j)//能放入時,選擇放與不放
                    dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
        printf("%d\n", dp[v]);

hdu-2546

題意是:一個總錢數爲m的錢包,在剩餘金額大於等於5的狀況下能夠購買任何東西,即便買了一個東西后剩餘錢數爲負,而後給你這n個東西的標價,每種東西只能購買一次,,

這道題按01揹包作的話,能夠將錢包m當作揹包的容量,n道菜就是n種物品, 每種物品的價值和花費都是其菜價,,

這是其中一個點,還有爲了儘量的是利益最大,,咱們能夠先保留5塊,爲了最後買那個最貴的菜,,對剩下的n-1個菜選擇出價值最大的,,,這樣就將這道題轉化成了容量爲m-5的揹包選擇一些物品使得總價值最大,,,最後的答案在算上那個最貴的菜就好了,,,

int dp[maxn], c[maxn], w[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n;
    while(scanf("%d", &n) && n)
    {
        for(int i = 1; i <= n; ++i)
            scanf("%d", &c[i]);

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

        if(m < 5)
        {
            printf("%d\n", m);
            continue;
        }
        m -= 5;
        sort(c + 1, c + 1 + n);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n - 1; ++i)
            for(int j = m; j >= c[i]; --j)
                dp[j] = max(dp[j], dp[j - c[i]] + c[i]);
        printf("%d\n", m + 5 - dp[m] - c[n]);
    }
    return 0;
}

hdu-1171

題意是:有一些設施,每一個設施的價值爲 \(w_i\),,而後要分紅兩堆,這兩堆的價值要儘量的相近

顯然分後的價值和 \(sum\) 就是原來的價值和,,而後確定一個大於等於均值,一個小於等於,,,因此能夠將這道題目當作01揹包的模型:一個容量爲 \(sum/2\) 的揹包,選擇裝一些物品,這些物品的價值的和費用相同,,求最大的價值

int dp[maxn], c[maxn], w[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n;
    while(scanf("%d", &n) && n > 0)
    {
        int tot = 0;
        for(int i = 1; i <= n; ++i)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            while(b--)w[++tot] = a;
        }
        memset(dp, 0, sizeof dp);
        int sum = 0;
        for(int i = 1; i <= tot; ++i)sum += w[i];
        for(int i = 1; i <= tot; ++i)
            for(int j = sum / 2; j >= w[i]; --j)
                dp[j] = max(dp[j], dp[j - w[i]] + w[i]);
        printf("%d %d\n", sum - dp[sum / 2], dp[sum / 2]);
    }
    return 0;
}

剩下一些其餘題,,之後再說

徹底揹包

徹底揹包就是在01揹包的基礎上對於物品的限制解除,,物品再也不爲只能取一件,而是無限件(實際也不多是無限件,每個物品最多取 \(\lfloor \frac{v}{c_i} \rfloor\)),,

將徹底揹包轉化爲01揹包後, 狀態轉移方程和01揹包的相似,,只有對揹包容量的枚舉也就是內層循環中,徹底揹包是遞增的順序而01揹包的是遞減的順序,,

\(for \ j=c_i \ to \ v\)

0-1揹包和徹底揹包的不一樣:

從二維數組上區別0-1揹包和徹底揹包也就是狀態轉移方程就差異在放第i中物品時,徹底揹包在選擇放這個物品時,最優解是F[i][j-c[i]]+w[i]即畫表格中同行的那一個,而0-1揹包比較的是F[i-1][j-c[i]]+w[i],上一行的那一個。

從一維數組上區別0-1揹包和徹底揹包差異就在循環順序上,0-1揹包必須逆序,由於這樣保證了不會重複選擇已經選擇的物品,而徹底揹包是順序,順序會覆蓋之前的狀態,因此存在選擇屢次的狀況,也符合徹底揹包的題意。狀態轉移方程都爲F[i] = max(F[i],dp[F-c[i]]+v[i])。

例題

hdu-1114

題意是:給你一個存錢罐的總質量個單純存錢罐的質量(也就是差爲錢的質量),,以及n種硬幣的面值和質量,而後問你最小的金額是多少

差值能夠看做揹包的容量,每一個硬幣的質量爲物品的代價,面值爲其價值,,而後求最小的價值轉移方程裏就爲min,,初始化再改變一下,,

int dp[maxn], c[maxn], w[maxn];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int t;scanf("%d", &t);
    while(t--)
    {
        int e, f;scanf("%d%d", &e, &f);
        int v = f - e;
        int k;scanf("%d", &k);
        for(int i = 1; i <= k; ++i)scanf("%d%d", &w[i], &c[i]);
        memset(dp, inf, sizeof dp);
        dp[0] = 0;
        for(int i = 1; i <= k; ++i)
            for(int j = c[i]; j <= v; ++j)
                dp[j] = min(dp[j], dp[j - c[i]] + w[i]);
        if(dp[v] >= inf)
            printf("This is impossible.\n");
        else
            printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]);
    }
    return 0;
}

多重揹包

多重揹包就是徹底揹包的限制版,,每一種物品再也不是無限個,,而是給定的個數,最後仍是求揹包的最大價值什麼的,,,

轉化成01揹包問題就是對於每一種物品取 \(1, 2, 2^2, 2^3,,,2^{k-1},M_i-2^k+1\)件,,

通常的多重揹包模板:

int dp[maxn], c[maxn], w[maxn], num[maxn];
int n, m, v;//n爲物品總數,v爲揹包容量
//01揹包,該物品的代價,價值
void ZeroOnePack(int C, int W)
{
    for(int i = v; i >= C; --i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//徹底揹包,該物品的代價,價值
void CompletePack(int C, int W)
{
    for(int i = C; i <= v; ++i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//一次多重揹包,該物品的代價,價值,數量
void OneMuitPack(int C, int W, int M)
{
    if(v <= C * M)//物品足夠多時用徹底揹包
    {
        CompletePack(C, W);
        return;
    }
    else        //不然用二進制劃分紅若干件01揹包的物品
    {
        int k = 1;
        while(k < M)
        {
            ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品
            M -= k;
            k <<= 1;
        }
        ZeroOnePack(C * M, W * M);//剩下的一些物品
    }
    return;
}

例題

hdu-2844

題意是:n種面值的硬幣,每種硬幣的個數限定,問你可以組成幾種面值和不超過m的組成方法,

轉化成揹包問題就是,一個容量爲m的揹包裝一些價值和代價都爲面值的物品,其中物品的個數有限制,,問揹包內的價值的可能種類

//cf
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <algorithm>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, ull> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e5 + 5;
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
int dp[maxn], c[maxn], w[maxn], num[maxn];
int n, m, v;//n爲物品總數,v爲揹包容量
//01揹包,該物品的代價,價值
void ZeroOnePack(int C, int W)
{
    for(int i = v; i >= C; --i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//徹底揹包,該物品的代價,價值
void CompletePack(int C, int W)
{
    for(int i = C; i <= v; ++i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//一次多重揹包,該物品的代價,價值,數量
void OneMuitPack(int C, int W, int M)
{
    if(v <= C * M)//物品足夠多時用徹底揹包
    {
        CompletePack(C, W);
        return;
    }
    else        //不然用二進制劃分紅若干件01揹包的物品
    {
        int k = 1;
        while(k < M)
        {
            ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品
            M -= k;
            k <<= 1;
        }
        ZeroOnePack(C * M, W * M);//剩下的一些物品
    }
    return;
}
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    while(scanf("%d%d", &n, &m) && n + m)
    {
        for(int i = 1; i <= n; ++i)scanf("%d", &w[i]);
        for(int i = 1; i <= n; ++i)scanf("%d", &num[i]);
        memset(dp, 0, sizeof dp);
        v = m;
        for(int i = 1; i <= n; ++i)
            OneMuitPack(w[i], w[i], num[i]);
        int ans = 0;
        for(int i = 1; i <= m; ++i)if(dp[i] == i)++ans;
        printf("%d\n", ans);
    }
    return 0;
}

混合揹包

混合揹包就是n種物品有的只能取一次,有的能取有限次,有的能取無限次,而後問你對於容量爲v的揹包的可取最大價值是多少

直接判斷每一個物品的種類,使用不一樣的揹包類型就好了

例題

codevs-3269

題意就是混合揹包的定義,,直接作就行

//cf
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <algorithm>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, ull> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 2e5 + 5;
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
int dp[maxn], c[maxn], w[maxn], num[maxn];
int n, m, v;//n爲物品總數,v爲揹包容量
//01揹包,該物品的代價,價值
void ZeroOnePack(int C, int W)
{
    for(int i = v; i >= C; --i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//徹底揹包,該物品的代價,價值
void CompletePack(int C, int W)
{
    for(int i = C; i <= v; ++i)
        dp[i] = max(dp[i], dp[i - C] + W);
    return;
}
//一次多重揹包,該物品的代價,價值,數量
void OneMuitPack(int C, int W, int M)
{
    if(v <= C * M)//物品足夠多時用徹底揹包
    {
        CompletePack(C, W);
        return;
    }
    else        //不然用二進制劃分紅若干件01揹包的物品
    {
        int k = 1;
        while(k < M)
        {
            ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品
            M -= k;
            k <<= 1;
        }
        ZeroOnePack(C * M, W * M);//剩下的一些物品
    }
    return;
}
int main()
{
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    scanf("%d%d", &n, &v);
    for(int i = 1; i <= n; ++i)scanf("%d%d%d", &c[i], &w[i], &num[i]);
    memset(dp, 0, sizeof dp);
    for(int i = 1; i <= n; ++i)
    {
        if(num[i] == 1)
            ZeroOnePack(c[i], w[i]);
        else if(num[i] == -1)
            CompletePack(c[i], w[i]);
        else
            OneMuitPack(c[i], w[i], num[i]);
    }
    printf("%d\n", dp[v]);
    return 0;
}

二維費用揹包

二維費用指的就是相比以前的揹包問題侑多了一個費用的影響因素,,對於一個物品有兩個不一樣的代價以及其容量,,作法和前面的同樣,dp數組增長一維就好了,,

例題

hdu-2159

轉化成揹包問題就是代價一是忍耐度,揹包容量爲m;代價二就是打怪,容量就是s,,求最大的價值(經驗值)與n的大小關係,,,

//cf
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <algorithm>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, ull> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e3 + 5;
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
int dp[maxn][maxn], c[maxn], w[maxn], num[maxn];
int n, m, v;//n爲物品總數,v爲揹包容量

int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n, m, k, s;
    while(~scanf("%d%d%d%d", &n, &m, &k, &s))
    {
        memset(w, 0, sizeof w);
        memset(c, 0, sizeof c);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= k; ++i)
            scanf("%d%d", &w[i], &c[i]);
        int ans = inf;
        for(int i = 1; i <= k; ++i)
            for(int j = c[i]; j <= m; ++j)
                for(int k = 1; k <= s; ++k)
                {
                    dp[j][k] = max(dp[j][k], dp[j - c[i]][k - 1] + w[i]);
                    if(dp[j][k] >= n)ans = min(ans, j);
                }
        if(ans > m)printf("-1\n");
        else       printf("%d\n", m - ans);
    }
    return 0;
}

(loading)

分組揹包

分組揹包就是:一堆物品被劃分紅了K組,同一組的物品只能選擇一個,或者這組不選,其餘的條件和其餘揹包模型同樣,,

解決方法,再加一層對每組揹包的枚舉

僞代碼:

\(for \ k=1 \ to \ K\)

\(for \ v=V \ to \ V\)

\(for \ item \ i \ in \ group \ k\)

\(F[v]=max(F[v], F[v-C_i]+W_i)\)

例題

hdu-1712

題意就是有n節課,每一課上幾天的價值給你,,一共要上m節課,問最大的價值,,

把這道題當作容量爲m的揹包,裝分爲n組的物品最大的價值就行

int dp[maxn];
int main()
{
    int n, m;
    int a[maxn][maxn];
    while(scanf("%d%d", &n, &m) && n + m)
    {
        memset(a, 0, sizeof a);
        for(int i = 1; i <= n; ++i)
            for(int j = 1; j <= m; ++j)
                scanf("%d", &a[i][j]);
        memset(dp, 0, sizeof dp);
        for(int k = 1; k <= n; ++k)//枚舉組數
            for(int j = m; j >= 0; --j)//枚舉揹包的容量
                for(int i = 1; i <= m; ++i)//枚舉第k組的物品
                    if(i <= j)//保證能裝下
                    dp[j] = max(dp[j], dp[j - i] + a[k][i]);
        printf("%d\n", dp[m]);
    }
    return 0;
}

hdu-3033

題意就是一堆鞋子,某一些是一個牌子的,而後每一雙鞋有一個價格(看做代價),一個價值,每一個牌子至少取一雙,問最大的價值,,,

與上一道不一樣的是每一組的物品再也不是最多選一個了,,一組能夠選多個,每一組都要選一個,,

dp[i][j]表示的是前i組在容量爲j的揹包所取的最大價值,,當前狀態dp[i][j]能夠由 前一狀態在本組選一個物品 推來,也能夠由 當前狀態在本組再取一個物品 推來,,

初始化也不一樣了,,除了那一組都不選的那一行dp爲零,,其餘都爲負,即未定義狀態,,由這個判斷是否有解,,

參考1
參考2
參考3

//hdu
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <algorithm>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e4 + 5;
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
int dp[11][maxn];
pii a[11][maxn];
int num[11];
int main()
{
//    freopen("233.in" , "r" , stdin);
//    freopen("233.out" , "w" , stdout);
//    ios_base::sync_with_stdio(0);
//    cin.tie(0);cout.tie(0);
    int n, m, K;
    while(~scanf("%d%d%d", &n, &m, &K))
    {
        memset(num, 0, sizeof num);
        for(int i = 1; i <= n; ++i)
        {
            int aa, bb, cc;
            scanf("%d%d%d", &aa, &bb, &cc);
            ++num[aa];
            a[aa][num[aa]].first = bb;
            a[aa][num[aa]].second = cc;
        }
        memset(dp, -1, sizeof dp);
        //for(int i = 0; i <= m; ++i)dp[0][i] = 0;
        memset(dp[0], 0, sizeof dp[0]);
        //不能寫成memset(dp[0], 0, sizeof dp);
        for(int k = 1; k <= K; ++k)
            for(int i = 1; i <= num[k]; ++i)
                for(int j = m; j >= a[k][i].first; --j)
                {
                    if(dp[k][j - a[k][i].first] >= 0)
                        dp[k][j] = max(dp[k][j], dp[k][j - a[k][i].first] + a[k][i].second);
                    if(dp[k - 1][j - a[k][i].first] >= 0)
                        dp[k][j] = max(dp[k - 1][j - a[k][i].first] + a[k][i].second, dp[k][j]);
                }
        if(dp[K][m] < 0)printf("Impossible\n");
        else            printf("%d\n", dp[K][m]);
    }
    return 0;
}

這道題沒怎麼理解還,,
(loading)

剩下一些其餘的內容,暫時先放放,,

相關文章
相關標籤/搜索