01背包 2016.5.1

来源:互联网 发布:华三交换机ip和mac绑定 编辑:程序博客网 时间:2024/05/16 16:02

参考《背包九讲》

一、题目

有 N 件物品和一个容量为 V 的背包

放入第 i 件物品耗费的空间是 Ci,得到的价值是 Wi

求解将哪些物品装入背包可使价值总和最大


二、基本思路

特点:

每种物品仅有一件,可以选择放或不放


用子问题定义状态:

dp[i,  V] 表示前 i 件物品恰放入一个容量为 v 的背包可以获得的最大价值


则其状态转移方程便是:
dp[i,  V] = max{dp[i − 1,  V],  dp[i − 1,  V − Ci]  +  Wi}


解释
“将前 i 件物品放入容量为 V 的背包中” 这个子问题
若只考虑第 i 件物品的策略(放或不放)
那么就可以转化为一个只和前 i − 1 件物品相关的问题

如果不放第 i 件物品,那么问题就转化为“ 前 i − 1 件物品放入容量为 V 的背包中 ”,价值为 dp[i − 1, V]

如果放第i件物品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为 V − Ci 的背包中”
此时能获得的最大价值就是 dp[i − 1,  V − Ci] 再加上通过放入第 i 件物品获得的价值 Wi


POJ 3624 Charm Bracelet


//时间和空间复杂度均为O(V * N)

//Result:MLE


#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 3410;int W[maxn];int D[maxn];int dp[maxn][12890];int main(){//    freopen("in.txt", "r", stdin);    int N, M;    scanf("%d%d", &N, &M);    for (int i=1; i<=N; ++i) {        scanf("%d%d", &W[i], &D[i]);    }    memset(dp, 0, sizeof(dp));    for (int i=1; i<=N; ++i) {        for (int j=1; j<W[i]; ++j) {            dp[i][j] = dp[i-1][j];        }        for (int j=W[i]; j<=M; ++j) {            dp[i][j] = max(dp[i-1][j-W[i]] + D[i], dp[i-1][j]);        }    }    printf("%d\n", dp[N][M]);    return 0;}


三、优化空间复杂度

以上方法的时间和空间复杂度均为O(V * N),其中时间复杂度应该已经不能再优化了,但空间复杂度却可以优化到O(V)


上面的基本思路:有一个主循环 i = 1..N,每次算出来二维数组 dp[i,  0..V ] 的所有值


那么,如果只用一个数组 dp[0..V ],能不能保证第 i 次循环结束后 dp[V] 中表示的就是我们定义的状态 dp[i, V] 呢?


dp[i, v] 是由 dp[i − 1, v] 和 dp[i − 1, v − Ci] 两个子问题递推而来

能否保证在推 dp[i, v] 时(也即在第 i 次主循环中推 dp[v] 时)能够取用 dp[i − 1, v] 和 dp[i − 1, v − Ci] 的值呢?


事实上,这要求在每次主循环中我们以 v = V..0 的递减顺序计算dp[v]

这样才能保证推 dp[v]时 dp[v − Ci] 保存的是状态 dp[i − 1,v − Ci] 的值


POJ 3624 Charm Bracelet


#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 3410;int W[maxn];int D[maxn];int dp[12890];int main(){//    freopen("in.txt", "r", stdin);    int N, M;    scanf("%d%d", &N, &M);    for (int i=1; i<=N; ++i) {        scanf("%d%d", &W[i], &D[i]);    }    memset(dp, 0, sizeof(dp));    for (int i=1; i<=N; ++i) {        for (int j=M; j>=W[i]; --j) {            if (dp[j-W[i]] + D[i] > dp[j]) {                dp[j] = dp[j-W[i]] + D[i];            }        }    }    printf("%d\n", dp[M]);    return 0;}


四、初始化的细节问题

求最优解的背包问题题目中,事实上有两种不太相同的问法


有的题目要求 “恰好装满背包” 时的最优解

有的题目则并没有要求必须把背包装满


一种区别这两种问法的实现方法是在初始化的时候有所不同


如果是第一种问法,要求恰好装满背包

那么在初始化时除了 dp[0] 为 0,其它 dp[1..V ] 均设为 −∞,这样就可以保证最终得到的 dp[V] 是一种恰好装满背包的最优解


如果并没有要求必须把背包装满,而是只希望价格尽量大

初始化时应该将 dp[0..V ] 全部设为 0


这是为什么呢?可以这样理解:

初始化的F 数组事实上就是在没有任何物品可以放入背包时的合法状态


如果要求背包恰好装满,那么此时只有容量为 0 的背包可以在什么也不装且价值为 0 的情况下被“恰好装满”

其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为 -∞ 了


如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”

这个解的价值为 0,所以初始时状态的值也就全部为 0 了


五、小结

01背包问题是最基本的背包问题

它包含了背包问题中设计状态、方程的最基本思想

另外,别的类型的背包问题往往也可以转换成01背包问题求解

故一定要仔细体会上面基本思路的得出方法状态转移方程的意义,以及空间复杂度怎样被优化


HDU 2602 Bone Collector


#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>using namespace std;typedef long long LL;const int maxn = 100 + 10;double Pj[maxn];int Mj[maxn];double dp[10010];int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);#endif // __AiR_H    int T;    while (scanf("%d", &T) != EOF) {        while (T--) {            double P;            int N;            int sum = 0;            scanf("%lf%d", &P, &N);            for (int i = 1; i <= N; ++i) {                scanf("%d%lf", &Mj[i], &Pj[i]);                sum += Mj[i];            }            memset(dp, 0, sizeof(dp));            dp[0] = 1;            for (int i = 1; i <= N; ++i) {                for (int j = sum; j >= Mj[i]; --j) {                    dp[j] = max(dp[j], dp[j - Mj[i]] * (1 - Pj[i]));                }            }            for (int i = sum; i >= 0; --i) {                if (dp[i] >= (1-P)){                    printf("%d\n", i);                    break;                }            }        }    }    return 0;}


HDU 2546 饭卡


解题思路:

最后买最贵的菜(可能有多个)

然后就是求 V = m-5  且不考虑第一个最贵饭菜的01背包问题


#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 1000 + 10;int price[maxn];int dp[1010];int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);#endif // __AiR_H    int n;    while (scanf("%d", &n) != EOF && n != 0) {        int Max;        int t;        for (int i = 1; i <= n; ++i) {            scanf("%d", &price[i]);            if (i == 1) {                Max = price[i];                t = 1;            } else if (price[i] > Max) {                Max = price[i];                t = i;            }        }        int m;        scanf("%d", &m);        if (m < 5) {            printf("%d\n", m);        } else {            memset(dp, 0, sizeof(dp));            for (int i = 1; i <= n; ++i) {                if (i != t) {                    for (int j = m-5; j >= price[i]; --j) {                        dp[j] = max(dp[j], dp[j-price[i]] + price[i]);                    }                }            }            printf("%d\n", m-dp[m-5]-Max);        }    }    return 0;}

HDU 1171 Big Event in HDU


转化为 V = halves 的01背包问题


#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int maxn = 50 + 10;int V[maxn];int M[maxn];int dp[130000];int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);#endif // __AiR_H    int N;    while (scanf("%d", &N) != EOF && N >= 0) {        int sum = 0;        for (int i = 1; i <= N; ++i) {            scanf("%d%d", &V[i], &M[i]);            sum += (V[i] * M[i]);        }        int halves = sum / 2;        memset(dp, 0, sizeof(dp));        for (int i = 1; i <= N; ++i) {            for (int j = 0; j < M[i]; ++j) {                for (int k = halves; k >= V[i]; --k) {                    dp[k] = max(dp[k], dp[k-V[i]] + V[i]);                }            }        }        printf("%d %d\n", sum-dp[halves], dp[halves]);    }    return 0;}





0 0