问题描述

有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. Matlab学习-(4)

    1. 函数 1.1 原始方法 之前我调用函数的方法是,首先写好函数文件,然后保存,然后在主函数中调用.这种方法的不足在于会导致你的工作目录的文件太多,从而导致很乱.在网上找了一些解决方法. 1.2 本 ...

  2. JavaScript数据类型 —— 基础语法(2)

    JavaScript基础语法(2) 数据类型 js中有六种数据类型,包括五种基本数据类型(Number,String,Boolean,Undefined,Null),和一种复杂数据类型(Object) ...

  3. MySQL服务端恶意读取客户端文件漏洞 (DDCTF2019和国赛均涉及到这个漏洞)

    mysql协议中流程和go语言实现的恶意mysql服务器:https://blog.csdn.net/ls1120704214/article/details/88174003 poc :https: ...

  4. kubernetes的cni0和flannel.1的关系?

    当容器运行之后,节点之间多了个虚拟接口cni0,它是由flanneld创建的一个虚拟网桥叫cni0,供pod本地通信使用.flanneld为每个pod创建一对veth虚拟设备,一端放在容器接口上,一端 ...

  5. 理解java容器底层原理--手动实现HashMap

    HashMap结构 HashMap的底层是数组+链表,百度百科找了张图: 先写个链表节点的类 package com.xzlf.collection2; public class Node { int ...

  6. 头文件<cmath>中常用函数

    <cmath>里面有很多数学函数,下面说一下常用的一些函数吧:直接把函数原型给了出来,用的时候注意参数 先说一下,c++自身是没有四舍五入函数round()的,若果你要用到的话,可以自己写 ...

  7. [Inno Setup] 退出安装程序的两种方式

    1. 完全静默的退出 procedure ExitProcess(exitCode:integer); external 'ExitProcess@kernel32.dll stdcall'; ... ...

  8. 怎么在java 8的map中使用stream

    怎么在java 8的map中使用stream 简介 Map是java中非常常用的一个集合类型,我们通常也需要去遍历Map去获取某些值,java 8引入了Stream的概念,那么我们怎么在Map中使用S ...

  9. HDU 1402 A*B

    #include <bits/stdc++.h> using namespace std; typedef long long ll; #define ms(s,a) memset(s,a ...

  10. 使用BottomNavigationView+ViewPager+Fragment的底部导航栏

    2019独角兽企业重金招聘Python工程师标准>>> 使用BottomNavigationView做底部工具栏,使用ViewPager做页面切换,使用Fragment完成每个页面的 ...