记录结果再利用的"动态规划"之背包问题

参考《挑战程序设计竞赛》p51

01背包问题

  • 问题描述:有n个重量和价值分别为wi、vi的物品,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

input:

4
5

2 3

1 2

3 4

2 2

output:

7(选择第0、1、 3号物品)

朴素解法:

c++版:

#include <iostream>
using namespace std;

int n,W;
int *w,*v;//数组的指针

int max(int x, int y)
{
    if (x>y) return x;
    return y;
}

int rec(int i, int j)//从数组下标为i的物品开始往后挑选总重小于j的物体,i从0开始
{
    int res;
    if (i==n) res=0;//没有物品了
    else if (j<w[i]) res=rec(i+1,j);//重量j小于该组物品的重量,不能取
    else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);//重量j大于该组物品的重量,能取;挑选和不挑选都尝试一下
    return res;
}

int main()
{
    cin >> n >> W;//n组物品,W:总重量
    w = new int[n];
    v = new int[n];
    for (int i=0; i<n; i++) cin >> w[i] >> v[i];
    cout << rec(0,W) << endl;
}

Java版本

package 记忆化搜索;

import java.util.Scanner;

public class Main {
	static int[] w, v;

	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		int W=sc.nextInt();
		w = new int[n];
	    v = new int[n];
	    for (int i=0; i<n; i++) {
	    	w[i]=sc.nextInt();
	    	v[i]=sc.nextInt();
	    }
	    System.out.println(rec(0,W));
	}

	private static int rec(int i, int j) {
		if (i==w.length) {
			return 0;
		}
		if (j<w[i]) {
			return rec(i+1, j);
		}
		int a=rec(i+1, j);
		int b=rec(i+1, j-w[i])+v[i];
		return Math.max(a, b);
	}

}


这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要O(2n)的时间。当n比较大时就没办法解了。所以要怎么办才好呢?为了优化之前的算法,我们看一下针对样例输人的情形下rec递归调用的情况。以下是rec(i,j)的模拟情况,i:第几组物品,j:重量

如图所示,rec以(3,2)为 参数调用了两次。如果参数相同,返回的结果也应该相同,于是第二次调用时已经知道了结果却白白浪费了计算时间。让我们在这里把第1次计算时的结果记录下来,省略掉第二次以后的重复计算试试看。

这微小的改进能降低多少复杂度呢?对于同样的参数,只会在第一次被调用到时执行递归部分,第二次之后都会直接返回。参数的组合不过nW种,而函数内只调用2次递归,所以只需要O(nW)的复杂度就能解决这个问题。只需略微改良,可解的问题的规模就可以大幅提高。这种方法一般被称为记忆化搜索。

c++版本:

#include <iostream>
#include <cstring>
using namespace std;

int n,W;
int *w,*v;
int **dp;

int max(int x, int y)
{
    if (x>y) return x;
    return y;
}

int rec(int i, int j)//从数组下标为i的物品开始往后挑选总重小于j的物体
{
    if (dp[i][j]>=0) return j[i[dp]];//和dp[i][j]的意义一样
    int res;
    if (i==n) res=0;
    else if (j<w[i]) res=rec(i+1,j);
    else res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
    dp[i][j] = res;
    return res;
}

int main()
{
    cin >> n >> W;
    w = new int[n];
    v = new int[n];
    dp = new int*[n+1];
    for (int i=0; i<=n; i++)
    {
        dp[i] = new int[W+1];
        memset(dp[i],-1,sizeof(int)*(W+1));
    }
    for (int i=0; i<n; i++) cin >> w[i] >> v[i];
    cout << rec(0,W) << endl;
}

 Java版本:

package 记忆化搜索;

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	static int[] w, v;
	static int[][] dp;
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		int W=sc.nextInt();
		w = new int[n];
	    v = new int[n];
	    for (int i=0; i<n; i++) {
	    	w[i]=sc.nextInt();
	    	v[i]=sc.nextInt();
	    }
	    dp=new int[n+1][W+1];
	    for (int i = 0; i < dp.length; i++) {
	    	Arrays.fill(dp[i], -1);
		}

	    System.out.println(rec(0,W));
	}

	private static int rec(int i, int j) {
		if (dp[i][j]>=0) {
			return dp[i][j];
		}
		if (i==w.length) {
			return 0;
		}
		if (j<w[i]) {
			return rec(i+1, j);
		}
		int a=rec(i+1, j);
		int b=rec(i+1, j-w[i])+v[i];
		int res=Math.max(a, b);
		dp[i][j]=res;
		return res;
	}

}

接下来,我们来仔细研究一下前面的算法利用到的这个记忆化数组。记dp[i][j]为 根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值。于是我们有如下递推式 :

dp[i+1][j] := 从前i+1个物品(即从编号为0到i这i+1个物品)中选出总重量不超过j的物品时总价值的最大值
dp[0][j] = 0
                 /  dp[i][j]  (j<w[i]时)
dp[i+1][j] = 
                 \  max(dp[i][j],dp[i][j-w[i]]+v[i])  (其它情况下)

如上所示,不同写递归函数,直接利用递推式将各项的值计算出来,简单地用二重循环也可以解决这一问题,复杂度为O(nW),与记忆化搜索是一样的,但是简洁了很多,这种方法叫做动态规划,即常说的DP:

c++版本解法:

#include <iostream>
#include <cstring>
using namespace std;

int n,W;
int *w,*v;
int **dp;

int max(int x, int y)
{
    if (x>y) return x;
    return y;
}

int main()
{
    cin >> n >> W;
    w = new int[n];
    v = new int[n];
    dp = new int*[n+1];
    for (int i=0; i<=n; i++)
    {
        dp[i] = new int[W+1];
        memset(dp[i],0,sizeof(int)*(W+1));
    }
    for (int i=0; i<n; i++) cin >> w[i] >> v[i];
    for (int i=0; i<n; i++)
    {
        for (int j=0; j<=W; j++)
        {
            if (j<w[i]) dp[i+1][j]=dp[i][j];
            else dp[i+1][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);
        }
    }
    cout << dp[n][W] << endl;
}

java版本:

参考代码:https://www.acwing.com/problem/content/submission/code_detail/3617/

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {

        Scanner scanner=new Scanner(System.in);
        int n=scanner.nextInt();
        int m=scanner.nextInt();
        int v[]=new int[n+1];
        int w[]=new int[n+1];
        for (int i = 0; i <n; i++) {
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
        }
        int f[][]=new int[n+1][m+1];
        for (int i = 0; i <n ; i++) {
            for (int j = 0; j <=m ; j++) {
                 if(j<v[i])
                f[i+1][j]=f[i][j];
                else
                f[i+1][j]=Math.max(f[i][j],f[i][j-v[i]]+w[i]);
            }
        }

        System.out.println(f[n][m]);
    }
} 

 

原文地址:https://www.cnblogs.com/clarencezzh/p/10368986.html

时间: 2024-10-09 07:35:11

记录结果再利用的"动态规划"之背包问题的相关文章

poj 2385 Apple Catching(记录结果再利用的动态规划)

传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题意: 有两颗苹果树,在每一时刻只有其中一棵苹果树会掉苹果,而Bessie可以在很短的时间内在两个苹果树间切换,但每一时刻只能切换一下: 求在1~T时刻,Bessie在最多可以切换W次的前提下最多可以获得多少苹果? 题解: 定义变量dp[ i ][ j ] : 前 i 时刻,移动 j 步所获得的最大的苹果数量: 据此写出状态转移方程: 如何判断在i处是否的到苹果呢? ①如果dp[i-1

437 - The Tower of Babylon(记录结果再利用DP)

最近准备进入动态规划的章节,仔细看了看紫书上对01背包的讲解,感觉很好..之前看<挑战程序设计竞赛>那本书,就没有讲的那么深刻 .      更加深刻的理解了什么叫记录结果再利用,手工操作了一遍01背包的过程,也有点明白它的状态是如何转移的了,而且那个状态方程所构成的递推关系真的很巧妙 . 言归正传..这道题就是嵌套矩形问题稍微改了一下,之前的嵌套矩形只需要维护一个状态量就行了,但是这道题是立方体,可以翻转的,所以还要维护一个特征量--高. 由于矩形的嵌套关系,每个状态只会出现一次,所以这么表

动态规划——0-1背包问题

 0-1背包问题: 描述:给定n中物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为c,问应如何选择装入背包中的物品,使得装入背包中的物品总价值最大? 0-1背包问题是一个特殊的整数规划问题. 设所给0-1背包问题的子问题; 其最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,-,n时0-1背包问题的最优值.由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下: NO1:递归实现 1 /* 2 *Description 递归实现 3 *设有一

动态规划:背包问题

例题:装箱问题 ( http://www.wikioi.com/problem/1014/  ) 题目描述 有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数). 要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小. 输入描述 一个整数v,表示箱子容量,一个整数n,表示有n个物品 接下来n个整数,分别表示这n 个物品的各自体积 输出描述 一个整数,表示箱子剩余空间. 样例输入 24 6 8 3 12 7 9 7

利用回溯法求解背包问题

最近看完了利用回溯法求八皇后问题,最后成功求解到92种解法,然后在看利用贪心求解背包问题,突然想到其实也可以利用回溯法求解背包问题,本质上回溯法是一个穷举的方式在求. 回溯法求解出的结果肯定是正确的,这也可以验证自己所写的贪心算法的正确性. 问题描诉: 设定Wmax为最大重量,W[](0~n-1)为编号0~n-1的货物重量,V[](0~n-1)为其价值,x[]为其中解, 在wn=ΣXi*Wi<Wmax的条件下,求Vmax=ΣXi*Vi. 代码如下: //全局变量最大价值int maxvalue=

算法导论三剑客之 动态规划 0-1背包问题

1 #include "iostream" 2 using namespace std; 3 4 float MAX(float m1,float m2){ 5 if(m1>=m2) 6 return m1; 7 else 8 return m2; 9 } 10 11 float bag_Zero_One(int n,float v,float p[],float w[]){ 12 if(n==0||v==0) 13 return 0; 14 else{ 15 float m2;

CSS Sprites技术(将背景图整合到一张图中,再利用CSS背景图片定位到要显示的位置)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Typ

动态规划01背包问题

动态规划0-1背包问题 ? 问题描写叙述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问应怎样选择装入背包的物品,使得装 入背包中物品的总价值最大? ? 对于一种物品,要么装入背包,要么不装.所以对于一种物品的装入状态能够取0和1.我们设物品i的装入状态为xi,xi∈ (0,1),此问题称为0-11背包问题. 过程分析 数据:物品个数n=5,物品重量w[n]={0,2,2,6,5,4},物品价值V[n]={0,6,3,5,4,6}, (第0位,置为0,不參与计算,仅

Dual Path Networks(DPN)——一种结合了ResNet和DenseNet优势的新型卷积网络结构。深度残差网络通过残差旁支通路再利用特征,但残差通道不善于探索新特征。密集连接网络通过密集连接通路探索新特征,但有高冗余度。

如何评价Dual Path Networks(DPN)? 论文链接:https://arxiv.org/pdf/1707.01629v1.pdf在ImagNet-1k数据集上,浅DPN超过了最好的ResNeXt-101(64×4d),具有26%更小的模型尺寸,25%的计算成本和8%的更低的内存消耗 5 个回答 xiaozhi CV.ML.DL 1.针对视觉识别任务的"网络工程"一直是研究的重点,其重在设计更为高效的网络拓扑结构,一方面考虑更好的特征表示学习,另一方面尽可能减少计算复杂度