所谓01背包问题,是指对于一定数量( i )的物品有一个容量为( j )的背包,每个物品都有自己的容量( k )、价值(value)。在保证物品容量之和不大于背包容量的前提下,如何选取物品得到最大价值?注意每个物品只能取一次,所以说是每个物品不是0个就是1个,称为01背包问题。

比如说给出最大容量是6,三个物品的容量和价值分别是2,5;3,8;4,9。则取容量是2和4的物品得到的价值最大,为14。这里用到的是经典的动态规划思想( DP )。

分解问题,把物品一件一件的放入背包内,实时记下当前所有可能容量的最大价值。如上面的例子中,先放容量2价值5的物品,容量在2-6的所有可能放下该物品的背包都存下该物品,用数组记录dp[1][[2]~dp[1][6]的值为5。余下的dp[1][1]保持0不变。

之后放入第二件物品,容量是3,价值是8。可能放下该物品的背包容量是3-6,此时需要判断在这些背包容量中该不该放该物品!容量是3的背包此时里面是刚才的容量为2的物品,把容量是3的物品放进去,价值会更高( 8>5 ),而容量是5和6的背包可以同时放下两个物品,最大价值就是5+8=13。用数组记录dp[2][3]~dp[2][4]的值为8,dp[2][5]~dp[2][6]的值为13。余下的dp[2][1]~dp[2][2]同dp[1][1]~dp[1][2]。

放入最后一件物品,容量4,价值9。可能放下该物品的背包容量是4-6,同上面的方法,dp[3][4] = 9;dp[3][5] = 13;dp[3][6] = 14。( dp[2][2]+9 )

由此可以推出,每次存放物品都需要判断当前背包容量减去当前物品容量(k)的最大价值加上当前物品的价值( value )是否大于原价值。如果大于就刷新最大值。状态转移方程dp[ind][jnd]=Max(dp[ind-1][jnd-k]+value,dp[ind-1][jnd])。这个方程用在可能放下该物品的前提下!如果当前物品容量大于当前背包容量则不计算,存之前此背包容量的价值即可。dp[ind][jnd]=dp[ind-1][jnd]

存入物品个数( i )和总容量( j ),再得到i个物品的容量和价值后。跑一遍状态转移方程,时间和空间复杂度都是O( i*j )。

对于最多有1000个物品,背包最大容量是10000的01背包问题。

#include<stdio.h>
#define Max(a,b) (a>b?a:b)
int dp[1005][10005];
int k[1005], value[1005];
int main(int argc, char* argv[])
{int i, j;scanf("%d %d", &i, &j);for (int ind = 1; ind <= i; ++ind)scanf("%d %d", &k[ind], &value[ind]);for (int ind = 1; ind <= i; ++ind){for (int jnd = 1; jnd <= j; ++jnd){if (jnd - k[ind] < 0)dp[ind][jnd] = dp[ind - 1][jnd];elsedp[ind][jnd] = Max(dp[ind - 1][jnd], dp[ind - 1][jnd - k[ind]] + value[ind]);}}printf("%d\n", dp[i][j]);return 0;
}

dp数组是1005*10005,这就显得有些大了,如何优化空间复杂度呢?那就是神奇的滚动数组!上面用状态转移方程一层一层的存储下了每次放物品的所有容量的最大价值,这就会显得没有必要,因为最后需要的只有最后一行的最大容量的价值。所以说根据上一行得到下行数据后上一行的数据就是没有用处的了。于是用一个一维数组dp[10005]存储当前的各容量价值即可,每次输入一个物品后,根据它的容量和价值和dp数组历史值刷新dp数组就可以啦。

具体是怎么个滚动法呢,得到一个输入物品容量和价值后,从最大容量背包( j )到可以放下当前物品的最小背包容量( k )依次判断,dp[ind] = Max(dp[ind-k]+value,dp[ind]) 。后面这个状态转移方程和上面的相同,但是上面的所有可能背包容量必须从最大值到最小值。这里可能很难马上理解,因为如果从最小值开始,那么比它大的背包容量就可能取到多次当前物品!比如说在还是空的的容量是10的背包里放容量价值都是1的物品,dp[1] = 1, dp[2] = dp[1] +1 = 2。显然这里出错误了,之后一直到dp[10]一直会递增下去。错误的根本原因是,从dp[2]开始状态转移方程用到的dp[ind-k]并不是历史数据了,而是放入当前物品刷新之后的dp数组。因为ind-k肯定是小于ind的,所以应该从最大值开始滚动。这样每次滚动用到的历史数据都会保证是没放入当前物品的旧数据。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max(a,b) (a>b?a:b)
int dp[10005];
int main(int argc, char* argv[])
{int i, j;scanf("%d %d", &i, &j);while (i--){int k, value;scanf("%d %d", &k, &value);for (int ind = j; ind >= k; ind--)dp[ind] = Max(dp[ind - k] + value, dp[ind]);}printf("%d\n", dp[j]);return 0;
}

使用滚动数组动态规划,时间空间复杂度都多少有所优化,空间优化最为显著,缩小到O( j )。

**那么如何继续优化滚动数组呢?每一个物品的"滚动"都是背包最大容量到能装下物品的最小容量( k ),这会不会有冗余部分呢?考虑一下,在后面物品用不到之前滚动的历史数据时,之前滚动的时候就没必要滚动那么多了吧。用到的历史数据就是dp[ind-k]项,取极限情况的时候,后面所有物品的容量加起来一同放进背包时,背包剩余的容量( x )大于最小容量( k )。这种情况下当前物品的滚动就不必到 k 了,而应该取x。所以说优化后的滚动数组应用Max(k,x)代替k,在背包总容量较大时,优化会很显著,缺点是不能在输入的同时滚动了,因为需要待全部物品输入完毕后才能得到相应的x值。

简单的用数据说明一下,如果背包总容量是10,当前物品的容量是5,那么没有优化前的滚动数组应该从dp[10]~dp[5]。而后面的物品分别是容量为1和2,加起来总容量不过是3,当把容量是3的物品放进背包时,就需要判断到底值不值,用到了dp[10-3]=dp[7]的数据,那么dp[7]将是后面物品可能用的到的最小背包容量,也就是说后面可能用的到的只有dp[7]~dp[10]。所以经过优化当前容量为5的物品滚动数组的变化是dp[10]~dp[7]。

透彻明白DP是怎么解决背包问题的之后,理解这个优化方法应该比较轻松。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max(a,b) (a>b?a:b)
int dp[10005];
int k[1005], value[1005];
int main(int argc, char* argv[])
{int i, j;scanf("%d %d", &i, &j);int s = 0;for (int ind = 1; ind <= i; ind++){scanf("%d %d", &k[ind], &value[ind]);s = s + k[ind];}for (int ins = 1; ins <= i; ins++){s -= k[ins];for (int ind = j; ind >= Max(j - s, k[ins]); ind--)dp[ind] = Max(dp[ind - k[ins]] + value[ins], dp[ind]);}printf("%d\n", dp[j]);return 0;
}

s 是当前物品之后所有物品的容量之和,j-s 就是前面提到的 x 。

**进一步提出问题,要求在选取的物品容量之和等于背包总容量的前提下,得到最大价值。这就需要筛选掉背包内有空余容量的所有情况了,在现有解法的基础上只需要将dp[1]~dp[j]的初始值设定成无限小就可以了。在放第一个物品进背包的时候因为只有dp[0]的初始值是 0 ,所以只有恰好能放下这个物品的背包才会存下这个物品,之后每次放新物品都只能是之前放过物品的容量和恰好能放下当前物品容量的背包才会有效判断是否该刷新最大价值。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Max(a,b) (a>b?a:b)
int dp[10005];
int k[1005], value[1005];
int main(int argc, char* argv[])
{int i, j;scanf("%d %d", &i, &j);memset(dp + 1, -1, j * sizeof(int));int s = 0;for (int ind = 1; ind <= i; ind++){scanf("%d %d", &k[ind], &value[ind]);s = s + k[ind];}for (int ins = 1; ins <= i; ins++){s -= k[ins];for (int ind = j; ind >= Max(j - s, k[ins]); ind--)if (dp[ind - k[ins]] != -1)dp[ind] = Max(dp[ind - k[ins]] + value[ins], dp[ind]);}printf("%d\n", dp[j]);return 0;
}

用 -1 表示无穷小,当用到的历史数据 dp[ind-k] 是 -1 时直接跳过。

01背包:经典DP问题( 基本/滚动数组(优化)/恰好装满 )相关推荐

  1. dp笔记:关于DP算法和滚动数组优化的思考

    从网上总结了一些dp的套路以及对滚动数组的一些思考,现记录如下,希望以后回顾此类算法时会有所帮助. 目录 1.DP算法经验 1.DP算法核心: 2.DP算法类别以及例题 例1:三步问题 例2:最小路径 ...

  2. 完全背包:经典DP问题( 基本法/滚动数组法 )

    完全背包问题,在已知物品个数( i )和最大容量( j )后,从i个物品中选取最大价值总和.(每个物品可以选取无数次) 与01背包问题所不同的就在这个每个物品可以选取多次上了,原有的状态转移方程加上选 ...

  3. 信息学奥赛一本通1267:【例9.11】01背包问题(二维dp与滚动数组优化)

    [题目描述] 一个旅行者有一个最多能装 MM 公斤的背包,现在有 nn 件物品,它们的重量分别是W1,W2,...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,. ...

  4. 【牛客 - 188D 】愤怒(01滚动数组优化dp,括号匹配方案个数,tricks)

    题干: 小w很生气 小w有一个长为n的括号序列 愤怒小w想把这个括号序列分为两个括号序列 小w想让分为的这两个括号序列同时合法 小w想知道一共有多少种划分方案 (划分的意思是划分为两个子序列) 注意两 ...

  5. 01背包问题(当有的背包重量是非整数时)的递归(优化成动态规划+再用滚动数组优化)解法+一些动态规划(递归,搜索)的高级技巧

    当背包重量是整数时,动态规划可以用数组存储状态就可以了. 当背包重量是非整数时,用map存储状态就可以了!主要思路:  map(构造函数中参数comp是仿函数(或者叫函数对象))+递归优化(" ...

  6. 【NOIP2015提高组】子串 区间DP+滚动数组优化

    题意: 有两个仅包含小写英文字母的字符串 A 和 B. 现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串. 请问 ...

  7. HDU - 1024 Max Sum Plus Plus 最大m段子段和+滚动数组优化

    给定n个数字,求其中m段的最大值(段与段之间不用连续,但是一段中要连续) 例如:2 5 1 -2 2 3 -1五个数字中选2个,选择1和2 3这两段. dp[i][j]从前j个数字中选择i段,然后根据 ...

  8. 01背包一维dp数组的实现

    二维的01背包模板 #include <iostream> #include <cstdio> #include <cstring>using namespace ...

  9. 01背包经典例题详解

    转载自点击打开链接 首先01背包题目的雏形是 有N件物品和一个容量为V的背包.第i件物品的费用是c[i],价值是w[i].求解将哪些物品装入背包可使价值总和最大. 从这个题目中可以看出,01背包的特点 ...

最新文章

  1. python的__init__几种方法总结【转载】
  2. 层和 native_React-Native与小程序的底层框架比较
  3. 计算机网络学习笔记-1.2.3OSI参考模型(2)
  4. Dais-CMX系列现代计算机组成原理,01 十六位机运算器实验
  5. 虚拟机安装菜鸟教程(1)—CentOS6.4系统VMware安装及配置详细教程
  6. 多功能pdf编辑器PDF Office Max for mac
  7. 关键词词云怎么做_网友问:做独立站,怎么做关键词排名,怎么做客户流量?...
  8. tomcat监听activemq jms配置
  9. 【训练计划】--2019-05
  10. 增强安全性、支持跨页逻辑关系设定的电信运营级EnableQ在线问卷调查引擎V1.60来了......
  11. exchange 2010部署之一
  12. 三种常用数据标准化方法
  13. be [prove] a great boon to [for]
  14. python列转行函数_python 列转行
  15. Kubernetes知识整理
  16. AtCoder abc256全题解(区间合并模板、矩阵快速幂优化dp、线段树……)
  17. 吃自助最高境界:扶墙进,扶墙出
  18. 个人电脑做外网服务器(一)
  19. 微信网页授权之回调域名
  20. Field xxxService in xxxController required a bean of type ‘xxxService‘ that could not be found.

热门文章

  1. 为什么Spring中的bean默认都是单例模式?
  2. nacos的服务发现详解
  3. 海思 fw_printenv 和 fw_setenv 工具详解
  4. vim实用指南(一):光标移动(2)——行内、行间移动
  5. Delphi使用PasLibVlc播放视频
  6. HGE系列之五 管中窥豹(基础类别)
  7. 如何一个月学会C语言
  8. 历史上最全最常用的HTTP状态码(超详细)
  9. Spring Cloud-Feign设计原理
  10. 图解法求最优解的例题_用图解法求线性规划最优解