问题描述

有n个物品,第i种物品的价值为\(p_i\)重量为\(W_i\),选一些物品到一个容量为C的背包里,使得背包内物品在总重量不超过C的前提下,价值尽量大。

问题分析

 在之前我们了解贪心思想的时候曾经有过类似的题目那时候物品是可拆分的我们只需要选择单位重量最大的物品即可。但是在这里,每一个物品都是完整的,只能是装或者不装。我们分析,有n个物品,为\(a_1\)...\(a_n\)当对\(a_1\)做决策之后,\(a_2\)...\(a_n\)是类似的问题。发现它可以用递归去解,那样的话,可以用DP去求解吗。如果可以的话我们就需要找到他的决策或者它的子问题。
 针对\(a_1\),我们发现当\(W_1\)>C时,说明\(a_1\)无法装进背包,问题变为求\(a_2\)...\(a_n\)装进容量为C的背包求最大价值。当\(W_1\)<C,可以选择装入背包,则问题变为求\(a_2\)...\(a_n\)装进容量为C-\(W_1\)的背包,求最大价值这时候价值变为\(P_1\)+所求。另一种选择是不装入背包这时候问题变为求\(a_2\)...\(a_n\)装进容量为C的背包求最大价值。对于父问题(也就是这个问题)来说,最优值在这两者之间取较大值。然后把这个最优值记录下来。接下来的每一个都是这样的操作(子问题)。最终递归出口就是只剩最后一个,an...an,当\(W_n\)>C时不能装最大的价值就是0,\(W_n\)<=C时,最大价值就是\(P_n\)

问题求解

设dp[i][j]表示从\(a_i\)到\(a_n\)的最大价值。j为背包的剩余容量。所以j这里我们默认为了整数,潜在的,\(W_i\)应该也是整数。思考:当这里不是整数的话应该怎么办?)

 

\(dp[i][j]=\left\{
\begin{aligned}
dp[i+1][j],{W_i>C}\\
max{\left\{
\begin{aligned}dp[i+1][j],不装,{W_i<=j}\\
dp[i+1][j-{W_i}]+{P_i},装,{W_i<=j}\end{aligned}\right.}
\end{aligned}
\right.
\)

 

递归出口(函数初值):

\(dp[n][j]=\left\{
\begin{aligned}
0,{W_n>j}\\
{P_n},{W_n<j}
\end{aligned}
\right.
\)

Java代码实现

        /**
* DP问题处理01背包问题
* @param w 物品重量
* @param p 物品价值
* @param C 背包容量
* @param n 物品个数
*/
public static void test(int[] w,int[] p,int C,int n) {
int[][]dp=new int[n+1][C+1];
//初值
for(int i=0;i<=C;i++)
if(i>=w[n])
dp[n][i]=p[n];
//2~n-1
for(int i=n-1;i>=2;i--) {
for(int j=0;j<=C;j++) {
if(w[i]>j)
dp[i][j]=dp[i+1][j];
else {
int temp=dp[i+1][j-w[i]]+p[i];
if(temp>dp[i+1][j])
dp[i][j]=temp;
else
dp[i][j]=dp[i+1][j];
}
}
}
//求1,C
if(w[1]>C)
dp[1][C]=dp[2][C];
else {
int temp=dp[2][C-w[1]]+p[1];
if(temp>dp[2][C])
dp[1][C]=temp;
else
dp[1][C]=dp[2][C];
}
System.out.println("最优值为"+dp[1][C]);
//最优解
int j=C;
for(int i=1;i<n;i++) {
if(dp[i][j]==dp[i+1][j])
{
System.out.println(i+"不放");
}
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(dp[n][j]==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}

优化方向一:时间方面:因为是j是整数是跳跃式的,可以选择性的填表。

(DP它避免了很多重复计算,但有时候会计算无用的子问题就是做了许多无用计算。可以以这种思想进行优化。)

 主要想法就是记录计算的路径。因为对于需要计算的每一个(i,j)如果&W_i&<j那么就需要计算(i+1,j)和(i+1,j-&W_i&)。从(1,n)开始推,因为不知道数组的长度选取了ArrayList数据结构,但是不能用其中的迭代器,因为迭代器不能改变在数组变化的过程中。话不多说,代码如下。

	public static void test(int[] w,int[] p,int C,int n) {
class Point{
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
//计算所用路径
int[][]flag=new int[n+1][C+1];
ArrayList<Point> list=new ArrayList<Point>();
list.add(new Point(1,C));
int iter=0;
while(iter!=list.size()) {
Point temp = list.get(iter++);
if(temp.x>=n)
break;
if(flag[temp.x+1][temp.y]==0)//没有添加过
{
list.add(new Point(temp.x+1,temp.y));
flag[temp.x+1][temp.y]=1;
}
if(temp.y-w[temp.x]>0&&flag[temp.x+1][temp.y-w[temp.x]]==0)
{
list.add(new Point(temp.x+1,temp.y-w[temp.x]));
flag[temp.x+1][temp.y-w[temp.x]]=1;
}
}
int[][]dp=new int[n+1][C+1];
for(int i=list.size()-1;i>=0;i--) {
Point temp=list.get(i);
if(temp.x==n)
dp[temp.x][temp.y]=w[temp.x]>temp.y?0:p[temp.x];
else {
if(w[temp.x]>temp.y)//装不下
dp[temp.x][temp.y]=dp[temp.x+1][temp.y];
else {
int t=dp[temp.x+1][temp.y-w[temp.x]]+p[temp.x];
dp[temp.x][temp.y]=t>dp[temp.x+1][temp.y]?t:dp[temp.x+1][temp.y];
}
}
}
for(int i=1;i<dp.length;i++) {
for(int j=1;j<dp[0].length;j++) {
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
System.out.println("最优值为"+dp[1][C]);
//最优解
int j=C;
for(int i=1;i<n;i++) {
if(dp[i][j]==dp[i+1][j])
{
System.out.println(i+"不放");
}
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(dp[n][j]==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}

输出了不同的dp数组,结果对比如下:

public static void main(String[] args) {
int[] w= {0,2,1,2,3,3};
int[] p= {0,10,8,9,12,4};
test(w,p,7,5);
}

优化前:



优化后:

思考二:处理j(背包容量),w(重量)不为整数的时候,因为j不为整数了,它就没办法作为数组下标使用。

 主要思想:这个想法建立在选择性填表的优化之上,在做选择性填表这个优化的时候,我们将计算路径记录了下来,现在我们也一样先记录计算路径,只不过Point(内部类)中的y值用float表示。然后将dp这个表变为map的数据结构。使用map保存是最重要的就是键的确定,这个键需要保存对应到dp中(i,j)这两个信息。key=i*max+j,当max=max(max(w[i]),C)+1时,针对每一个(i,j)都有确定的唯一key。因为\(i_1*max+j_1=i_2*max+j_2\),(\((i_1-i_2)\)=\(\frac{(j_2-j_1)}{max}\)中右侧<1,左侧在[0,n-1]中取整数,只有(i,j)完全相同时取0)。这个相当于hashmap中的hash函数来映射一样。然后其他的步骤与选择性填表的填表过程类似。代码如下:

	public static void test(float[] w,float[] p,float C,int n) {
class Point{
int x;
float y;
public Point(int x, float y) {
this.x = x;
this.y = y;
}
}
//计算函数表达式系数
float max=w[0];
for(int i=1;i<w.length;i++) {
if(w[i]>max)
max=w[i];
}
max=Math.max(C, max)+1;
//计算所用路径
ArrayList<Point> list=new ArrayList<Point>();
Map<Float,Float> map=new HashMap<Float,Float>();
list.add(new Point(1,C));
int iter=0;
while(iter!=list.size()) {
Point temp = list.get(iter++);
if(temp.x>=n)
break;
if(!map.containsKey((temp.x+1)*max+temp.y))
{
list.add(new Point(temp.x+1,temp.y));
map.put((temp.x+1)*max+temp.y, (float) -1.0);
}
if(temp.y-w[temp.x]>=0&&!map.containsKey((temp.x+1)*max+(temp.y-w[temp.x])))
{
list.add(new Point(temp.x+1,temp.y-w[temp.x]));
map.put((temp.x+1)*max+(temp.y-w[temp.x]), (float) -1.0);
}
}
//填表
for(int i=list.size()-1;i>=0;i--) {
Point t=list.get(i);
if(t.x==n)
{
if(w[t.x]>t.y)map.put(t.x*max+t.y, (float) 0);
else map.put(t.x*max+t.y, p[t.x]);
}
else {
if(w[t.x]<=t.y)
{
float ft=p[t.x]+map.get((t.x+1)*max+(t.y-w[t.x]));
if(ft>map.get((t.x+1)*max+t.y))
map.put(t.x*max+t.y, ft);
else
map.put(t.x*max+t.y, map.get((t.x+1)*max+t.y));
}
else
map.put(t.x*max+t.y, map.get((t.x+1)*max+t.y));
}
}
System.out.println("最优值:"+map.get(max+C));
//最优解
float j=C;
for(int i=1;i<n;i++) {
if(map.get(i*max+j)==map.get((i+1)*max+j))//说明没装
System.out.println(i+"不放");
else {
System.out.println(i+"放");
j=j-w[i];
}
}
if(map.get(n*max+j)==0)
System.out.println(n+"不放");
else
System.out.println(n+"放");
}

总结

首先,这个问题的解决从递归到递推,因为i(开始的物品)和j(背包)的存在选用二维数组(i..n,n是一定的,所以只有两个参数)。

其次,我一开始想的是倒着推,从只有一个物品到n个这也是DP问题常用的想法,就是从小问题到大问题。

最后,DP问题的下手点可以先分析出它的子问题,但是这个子问题是来源与决策的。所以,决策也很重要。

DP动态规划之01背包问题的更多相关文章

  1. 动态规划入门-01背包问题 - poj3624

    2017-08-12 18:50:13 writer:pprp 对于最基础的动态规划01背包问题,都花了我好长时间去理解: poj3624是一个最基本的01背包问题: 题意:给你N个物品,给你一个容量 ...

  2. C++动态规划求解0-1背包问题

    问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应该如何选择装入背包的物品,是的装入背包中物品的总价值最大? 细节须知: 暂无. 算法原理: a.最优子结构性质 ...

  3. 动态规划专题 01背包问题详解 HDU 2546 饭卡

    我以此题为例,详细分析01背包问题,希望该题能够为大家对01背包问题的理解有所帮助,对这篇博文有什么问题可以向我提问,一同进步^_^ 饭卡 Time Limit: 5000/1000 MS (Java ...

  4. nyoj 49-开心的小明(动态规划, 0-1背包问题)

    49-开心的小明 内存限制:64MB 时间限制:1000ms Special Judge: No accepted:7 submit:11 题目描述: 小明今天很开心,家里购置的新房就要领钥匙了,新房 ...

  5. dp入门之01背包问题

    ...通过暴力手推得到的一点点感觉 动态规划是相对于贪心算法的一种取得最优解的算法,通过对每一步的取舍判断从 0 推到所拥有的第 n 件物品,每次判断可以列写出状态转移方程,通过记忆化相对暴力地取得最 ...

  6. dp或dfs(01背包问题)

    链接:https://ac.nowcoder.com/acm/contest/993/C来源:牛客网题意:n头牛,给出它们的H高度,问这些牛的高度叠加起来大于等于书架高度,问叠加后的高度与书架的差值最 ...

  7. ACM:动态规划,01背包问题

    题目: 有n件物品和一个容量为C的背包.(每种物品均仅仅有一件)第i件物品的体积是v[i],重量是w[i].选一些物品装到这个背包中,使得背包内物品在整体积不超过C的前提下重量尽量大. 解法:两种思路 ...

  8. 经典DP动规 0-1背包问题 二维与一维

    先上代码 b站讲解视频 灯神讲背包 #include <iostream> #include <cstring> #include <algorithm> usin ...

  9. 0-1背包问题——动态规划求解【Python】

    动态规划求解0-1背包问题: 问题:背包大小 w,物品个数 n,每个物品的重量与价值分别对应 w[i] 与 v[i],求放入背包中物品的总价值最大. 动态规划核心:计算并存储小问题的最优解,并将这些最 ...

随机推荐

  1. Cucumber(1) —— 环境配置

    目录 学习资料 cucumber简介 cucumber环境配置 学习资料 1.cucumber官方学习网站 cucumber简介 1.cucumber是一种支持BBD(behavior-driven ...

  2. linux 文件的查找和压缩

    1.使用 locate 命令 需要安装:yum install mlocate -y 创建或更新 slocate/locate 命令所必需的数据库文件:updatedb 作用:搜索不经常改变的文件如配 ...

  3. Os-hackNos-1靶机过关记录

    靶机地址:172.16.1.198(或112)  kali地址:172.16.1.108 1 信息收集 靶机界面如下 简单查看 OS:Ubuntu Web:Apache2.4.18 尝试端口扫描 开放 ...

  4. response没有实现跳转,而是提示浏览器下载文件

    问题简述: web项目中,response没能实现重定向跳转网页,而是通知浏览器下载文件. 代码如下: response.getWriter().write("<h1 style='c ...

  5. Java 动态编译--DynamicCompiler

    java 动态编译自己写过程的机会比较少,记录一下: package com.xzlf.dynamicCompile; import java.io.IOException; import java. ...

  6. 单线程下实现IO切换

    1.Greenlet greenlet可以实现两个任务之间的来回切换,但遇到IO会阻塞,不会切(使用这个模块之前需要在电脑命令提示符中输入 pip3 install greenlet 进行安装) 例如 ...

  7. Web前端三大主流框架是什么?Web前端前景与就业形势

    近十年以来,IT行业发展火热,衍生了很多新职业,例如UI设计师.开发工程师.软件测试工程师等等,在众多备受瞩目的新生职业中,Web前端工程师是其中的一员.那么Web前端三大主流框架是什么呢? 一.We ...

  8. Linux安全实验缓冲区溢出

    缓冲区溢出实验: 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关 ...

  9. Bat 脚本 删除某一行

    findstr /v /i /c:"kiwi" /c:"oranges" myfile.txt >newfile.txt

  10. [USACO3.2]魔板 Magic Squares

    松下问童子,言师采药去. 只在此山中,云深不知处.--贾岛 题目:魔板 Magic Squares 网址:https://www.luogu.com.cn/problem/P2730 这是一张有8个大 ...