背包问题之零一背包

注:参考文献《背包九讲》.

零一背包问题

一:题目描述

  有 N 件物品和一个容量为 V 的背包.放入第 i 件物品耗用的费用为Ci(即所占用背包的体积),得到的价值是 Wi.求将哪些物品装入背包所得到的总价值最大.

二:基本思路

  01背包是最基础的背包问题,这道题的特点是每种物品仅有一件,可以选择放或不放,且不要求背包必须被放满,只要求最后的总价值最大.

  用子问题定义状态:F[i][v] 表示对于前 i 件物品,当背包容量为 v 时所能得到的价值最大值.设想,将 "前 i 件物品放入容量为 v 的背包中" 这个子问题,若只考虑第 i 件物品的策略(要么放要么不放),那么就可以转化为一个之和前 i - 1 件物品相关的问题.如果不放第 i 件物品, 那么问题就转化为 ”前 i - 1 件物品放入容量为  v 的背包中“,价值就是 F[i - 1][v]; 如果放第 i 件物品,那么问题就转化为 ”前 i  - 1 件物品放入剩下的容量为 v - Ci 的背包中”, 此时获得的价值为 F[i - 1][v - Ci] + Wi.分析到这里则可得状态转移方程为:

                F[i][v] = max( F[i - 1][v], F[i - 1][v - Ci] + Wi ).

在这里要特别的说明一下,这个方程非常重要,一定要知道这是怎么推出来的,几乎后面的所有的背包问题都和这个方程有着密不可分的联系.

伪代码如下:

F[0...N][0...V]  <---  0

for i  <--- 1  to  N

  for v <--- Ci to V

    F[i][v] = max( F[i - 1][v], F[i - 1][v - Ci] + Wi );

具体代码:

1 void _01Pack(int F[][MAXV], int N, int V, int C[], int W[]){
2     memset(F, 0, sizeof(F));
3     for(int i = 1; i <= N; i++) {
4         for(int v = C[i]; v <= V; v++) {
5             F[i][v] = max(F[i - 1][v], F[i - 1][v - C[i]] + W[i]); //放或者不放两者之中选择最优者
6         }
7     }
8 }

三:优化空间复杂度

  可以清楚的看到上面算法的时间复杂度和空间复杂度均为 O(N * V), 这里时间复杂度已经不能得到优化,但是空间复杂度确可以优化到 O(V).

  先看上面代码是如何实现的.最外面一层循环,每次计算出二维数组 F[i][0...V] 的值,计算的时候 F[i][0...V] 是由它的上一层 F[i  - 1][0...V] 而得到的.那么如果把这个数组换成一维的 F[v] 那么还能保留上一次的状态吗.答案是可以的.由于动态规划算法的无后效性,第 i + 1 件物品的选择与否不会影响到第 i 件物品(即它的前一件物品)的选择状态.那么可以在上面第二次循环中按照 v <--- V...0 递减的顺序来计算 F[v], 这样计算 F[v] 时所需要的状态 F[v] 和 F[v - Ci] + Wi 仍然还是上一次的状态.而计算 F[v] 之后, v 的顺序是递减的, F[v] 不会影响到 F[v‘] (v‘ < v), 因为F[v‘] 只与 F[v‘](上一次的值) 和 F[v - Ci] 有关, 而 F[v] > F[v‘] > F[v‘ - Ci]. 所以又可得状态转移方程.

                F[v] = max( F[v], F[v - Ci] + Wi ).

伪代码如下:

F[0...V]  <---  0

for i  <--- 1  to  N

  for v <--- V  to  Ci

    F[v] = max( F[v], F[v - Ci] + Wi );

具体代码:

1 void _01Pack(int F[], int N, int V, int C[], int W[]){
2     memset(F, 0, sizeof(F));
3     for(int i = 1; i <= N; i++) {
4         for(int v = V; v >= C[i]; v--) {
5             F[i][v] = max(F[v], F[v - C[i]] + W[i]);
6         }
7     }
8 }

可以看到从第一个状态转移方程到第二个状态转移方程的空间优化效率还是挺大的:

      F[i][v] = max( F[i - 1][v], F[i - 1][v - Ci] + Wi ).      ---->     F[v] = max( F[v], F[v - Ci] + Wi ).

在第二个方程中 F[v]1 = max(F[v]2, F[v - Ci] + Wi), 其实 F[v]就相当与方程一中的 F[i - 1][v], 对应的 F[v - Ci] + Wi 就相当于 F[i  -1][v - Ci] + Wi.这一正确性是在内层循环递减的前提下才成立的.否则, 将内层循环改为递增, 那么 F[i][v] 其实是由 F[i][v] 和 F[i][v - Ci] 推出来的,这不符合基本思路中的探讨.

之前说过由于 01背包 的特殊性,这里将 01背包 抽象化,方便之后的调用.

解决单个物品 01背包 的伪代码:

def ZeroOnePack (F, C, W)

  for v  <---  V  to  C

    F[v] = max( F[v], F[v - C] + W );

这么写之后, 01背包总问题解决的伪代码就可以改写成:

F[0...V]  <--- 0

for  i  <--- 1  to N

  ZeroOnePack(F, C[i], W[i]);

具体代码:

 1 const int MAXN = 10000;
 2 int N, V, C[MAXN], W[MAXN];
 3
 4 void ZeroOnePack(int F[], int C, int W) { // 对于单个物品的决策
 5     for(int v = V; v >= C; v--) {
 6         F[v] = max(F[v], F[v- C] + W);
 7     }
 8 }
 9
10 void solv(int F[]) {
11     memset(F, 0, sizeof(F));
12     for(int i = 1; i <= V; i++) {
13         ZeroOnePack(F, C[i], W[i]);
14     }
15 }

四: 01背包问题的拓展 ------ 初始化的细节问题

  在上述 01背包的问题中

时间: 2024-08-29 01:21:59

背包问题之零一背包的相关文章

多重背包问题(来源:背包九讲)

问题: 有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 基本算法: 这题目和全然背包问题非常类似.主要的方程仅仅需将全然背包问题的方程稍微一改就可以,由于对于第i种物品有n[i]+1种策略:取0件,取1件--取n[i]件.令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w

背包问题基本解法 —— 《背包九讲》笔记

相对于转载文章,我更喜欢写上一篇笔记,开篇给出原文链接.这样,能有些自己的东西,总结一番,对知识的理解能加深一层:别人看来,也更有价值. 今天做USACO题目时,一道题不会,网上查到解法是01背包,于是重新看了<背包九讲>.相比第一次看,理解深的多,可见我还是在进步的,只要我没停下脚步.如果大家想看原文,那么只需要百度“背包九讲”就好了,百度文库中的“背包九讲 2.0”是正版,作者是崔添翼前辈,网上好像称他为dd大牛.这篇文章可以说是“背包问题”的权威了,如果我了解无误的话,背包问题的整套解法

【动态规划】【零一背包】CODEVS 1014 装箱问题 2001年NOIP全国联赛普及组

1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int n,m,w[31],f[30001]; 5 int main() 6 { 7 scanf("%d%d",&m,&n); 8 for(int i=1;i<=n;i++) scanf("%d",&w[i]); 9 for(int i=1;i<=n;i++) 10 for(i

零基础学贪心算法

本文在写作过程中参考了大量资料,不能一一列举,还请见谅.贪心算法的定义:贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解.贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关.解题的一般步骤是:1.建立数学模型来描述问题:2.把求解的问题分成若干个子问题:3.对每一子问题求解,得到子问题的局部最优解:4.把子问题的局部最优

从零开始学贪心算法

本文在写作过程中参考了大量资料,不能一一列举,还请见谅. 贪心算法的定义: 贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解.贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关. 解题的一般步骤是: 1.建立数学模型来描述问题: 2.把求解的问题分成若干个子问题: 3.对每一子问题求解,得到子问题的局部最优解: 4.把子

二维费用背包问题(背包九讲)

------------------------------------------ 前言: 对于一些背包问题,重点还是在于如何找出"背包容量"和"各种代价",以及价值,如此问题便迎刃而解了.下午 打篮球居然下冰雹了,悲催了.... ------------------------------------------ 问题: 二维费用的背包问题是指:对于每件物品,具有两种不同的费用:选择这件物品必须同时付出这两种代价:对于每种代价都有 一个可付出的最大值(背包容量)

背包问题(01背包,完全背包,多重背包)

转自:http://www.cnblogs.com/daoluanxiaozi/archive/2012/05/06/2486105.html 背包问题,经典有背包九讲. 01背包 不死族的巫妖王发工资拉,死亡骑士拿到一张N元的钞票(记住,只有一张钞票),为了防止自己在战斗中频繁的死掉,他决定给自己买一些道具,于是他来到了地精商店前. 死亡骑士:"我要买道具!" 地精商人:"我们这里有三种道具,血瓶150块一个,魔法药200块一个,无敌药水350块一个." 死亡骑士

【转】 背包问题——“完全背包”详解及实现(包含背包具体物品的求解)

完全背包是在N种物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里,每种物品的体积为C1,C2,…,Cn,与之相对应的价值为W1,W2,…,Wn.求解怎么装物品可使背包里物品总价值最大. 动态规划(DP): 1) 子问题定义:F[i][j]表示前i种物品中选取若干件物品放入剩余空间为j的背包中所能得到的最大价值. 2) 根据第i种物品放多少件进行决策      (备注:应该还有一项f[i-1][j])        (2-1) 其中F[i-1][j-K*C[i]]+K*W[i]表示前i

代码与算法集锦-归并排序+树状数组+快排+深度优先搜索+01背包(动态规划)

归并排序 求逆序数 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 首先考虑下如何将将二个有序数列合并.这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数.然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可. //将有序数组a[]和b[]合并到c[]中 void MemeryArray(int a[], int n, int b[], int m, int c