2021寒假每日一题《01背包问题》

01背包问题

题目来源:背包九讲
时间限制:1000ms 内存限制:64mb

题目描述

N N N 件物品和一个容量是 V V V 的背包。每件物品 只能使用一次
i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数, N N N V V V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N N N 行,每行两个整数 v i v_i vi, w i w_i wi,用空格隔开,分别表示第 i i i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N N N, V V V ≤ 1000
0 < v i v_i vi, w i w_i wi ≤ 1000

样例输入

4 5
1 2
2 4
3 4
4 5

样例输出

8

解题思路1:暴力破解

尝试各种可能的商品组合,并找出价值最高的组合。
使用 N N N 位二进制字串表示物品是否放入背包,枚举所有的可能,然后算出每种可能的价值,取其最大值输出。
解法不足:速度非常慢。在只有3件商品的情况下,你需要计算8个不同的集合;当有4件商品的时候,你需要计算16个不同的集合。每增加一件商品,需要计算的集合数都将翻倍。
对于每一件商品,都有选或不选两种可能,即这种算法的运行时间是 O ( 2 n ) O(2^n) O(2n)
IO竞赛(例如:蓝桥杯)当中,如果实在想不起来动态规划,可以使用这个方法拿到一部分分数,但是在ACM竞赛当中,就不能使用这种方法了。

解题代码1-Java

import java.util.*;

public class Main {
    
    
    static int getSum(int n, int v, StringBuilder binString, int[][] items) {
    
    
        int sumV = 0, sumN = 0;
        for (int j = 0; j < n; j++) {
    
    
            if (binString.charAt(j) == '1') {
    
    
                sumV += items[j][0];
                sumN += items[j][1];
            }
            if (sumV > v) {
    
    
                return -1;
            }
        }
        return sumN;
    }

    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int v = input.nextInt();
        int totalV = 0, totalN = 0;
        int[][] items = new int[n][2];
        for (int i = 0; i < n; i++) {
    
    
            items[i][0] = input.nextInt();
            items[i][1] = input.nextInt();
            totalV += items[i][0];
            totalN += items[i][1];
        }
        input.close();
        if (totalV <= v) {
    
    
            System.out.println(totalN);
            return;
        }
        int sumN = 0;
        for (long i = 0; i < Math.pow(2, n); i++) {
    
    
            StringBuilder binString = new StringBuilder(Long.toBinaryString(i));
            long len = binString.length();
            while (len < n) {
    
    
                binString.insert(0, "0");
                len++;
            }
            sumN = Math.max(sumN, getSum(n, v, binString, items));
        }
        System.out.println(sumN);
    }
}

解题思路2:动态规划

对于动态规划算法,可以去这篇文章里学习:经典中的经典算法:动态规划(详细解释,从入门到实践,逐步讲解)
每个动态规划都从一个网格开始。
在本题中,网格的各行表示商品,各列代表不同容量的背包(从1到V)。
网格最初是空的。你将填充其中的每个格子,网格填满后,就找到了问题的答案!
比如本题样例的网格如下图:
图1:初始网格

一、画好网格之后,先来看第一行。

在每个一格子你都要做出一个选择:放不放进这一行对应的物品。
物品1所占空间为 1 1 1 ,也就是说物品1能放进容量为 1 1 1 的这个背包,因此这个单元格包含物品1。所以往第一行第一列中装入物品1,价值为2。
图2:填充第一行第一列
这行的其他单元格也一样。别忘了,这是第一行,只有一个物品可供你选择,换而言之,你假装现在还没有打算放进其他物品。
填充完之后如下图:
图3:填充完第一行

此时你很可能心存疑惑:原题说的是容量为5的背包,我们为何要考虑容量为1、2、3、4的背包呢?**动态规划从子问题着手,逐步解决大问题。**这里解决的子问题将帮助你解决大问题。
**这行表示的是当前的最大价值,**并不是最终解。随着算法往下执行,将逐步修改最大价值。

二、接下来看第二行。

在第二行中,你有两种物品选择。先看第一个单元格,容量为1,之前装进去的最大的价值为2。
这一个单元格该不该放入第二个物品呢?答案显然是不行,因为容量为1的口袋无法装下占空间2的物品。
因此第一列的最大价值保持不变。
图4:填充第二行第一列
接下来看第二行第二列,这个格子所对应背包的容量为2,现在能够装下第二个物品了,对比一下价值,比之前决定的物品1价值高,所以将原来的物品1换为物品2。
再看后面的第三列,这个格子容量为3,可以同时装下物品1和物品2,所以都放进去。
之后的列也进行一样的操作,最后操作完第二行的结果为:
图5:填充完第二行

三、做完第二行,接下来看第三行

以同样的方式处理物品3,物品3占用空间3,价值为4。
其中在第五列时,容量为5,原本决定的为(物品1+物品2),现在有物品3可以选择了,所以考虑将物品1换为价值更高的物品3,所以此行第五列结果为8
做完这一步就得到了下面的网格:
图6:填充完第三行

扫描二维码关注公众号,回复: 12469048 查看本文章

四、第四行也一样

图7:填充完第四行

五、最终结果

可以总结得到一个公式: c e l l [ i ] [ j ] ( i 和 j 代 指 行 和 列 ) = 两 者 中 较 大 的 一 项 { 1. 上 一 个 单 元 格 的 值 ( 即 : c e l l [ i − 1 ] [ j ] 的 值 ) 2. 当 前 物 品 的 价 值 + 剩 余 空 间 的 价 值 ( 即 : c e l l [ i − 1 ] [ j − 当 前 物 品 的 占 用 空 间 ] + 当 前 物 品 的 价 值 ) cell[i][j](i和j代指行和列) = 两者中较大的一项 \begin{cases} 1.上一个单元格的值(即:cell[i-1][j]的值) \\ 2.当前物品的价值+剩余空间的价值(即:cell[i-1][j-当前物品的占用空间] + 当前物品的价值) \end{cases} cell[i][j](ij)={ 1.cell[i1][j]2.+cell[i1][j]+
你可以使用这个公式来计算每个单元格的价值,最终的网格将与前一个网格相同。
这里回答前面抛出的问题:为什么要求解子问题?——因为你可以合并两个子问题的解来得到更大问题的解。
相信看到这里,并且亲手推导过网格,应该对动态规划的状态转移方程背后的逻辑有了更深的理解。现在,再回头看01背包问题的经典描述,并实现代码。

六、程序实现

声明一个数组dp[n+1][v+1]表示初始网格,首行为0,表示不放入任何物品,同时也为了代码阅读性,从下标1开始处理。
图8:dp初始
根据第五步的公式,对于编号为 i i i 的物品:

  • 如果将 i i i放入, 当 前 背 包 的 最 大 价 值 = 第 i 号 物 品 的 价 值 + 出 去 i 号 物 品 占 用 空 间 后 剩 余 的 空 间 所 能 存 放 的 最 大 价 值 当前背包的最大价值 = 第i号物品的价值 + 出去i号物品占用空间后剩余的空间所能存放的最大价值 =i+i
    即: v a l u e W i t h _ i = w i + d p [ i − 1 ] [ j − v i ] ; valueWith\_i = w_i + dp[i-1][j-v_i]; valueWith_i=wi+dp[i1][jvi];
  • 如果不放入 i i i 当 前 背 包 的 价 值 = 前 i − 1 个 物 品 存 放 在 背 包 中 的 最 大 价 值 当前背包的价值 = 前i-1个物品存放在背包中的最大价值 =i1
    即: v a l u e W i t h o u t _ i = d p [ i − 1 ] [ j ] ; valueWithout\_i = dp[i-1][j]; valueWithout_i=dp[i1][j];
  • 最终, d p [ i ] [ j ] dp[i][j] dp[i][j]的结果取两者的较大值。
    即: d p [ i ] [ j ] = M a t h . m a x ( v a l u e W i t h _ i , v a l u e W i t h o u t _ i ) ; dp[i][j] = Math.max(valueWith\_i, valueWithout\_i); dp[i][j]=Math.max(valueWith_i,valueWithout_i);

解题代码2-Java

import java.util.*;

public class Main {
    
    
    static int maxValue(int n, int v, int[][] items) {
    
    
        if (n == 0) {
    
    
            return 0;
        }
        int[][] dp = new int[n + 1][v + 1];
        for (int i = 1; i <= n; i++) {
    
    
            for (int j = 1; j <= v; j++) {
    
    
                int valueWith_i = (j - items[i - 1][0] >= 0) ? (items[i - 1][1] + dp[i - 1][j - items[i - 1][0]]) : 0;
                int valueWithout_i = dp[i - 1][j];
                dp[i][j] = Math.max(valueWith_i, valueWithout_i);
            }
        }
        return dp[n][v];
    }

    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int v = input.nextInt();
        int totalV = 0, totalN = 0;
        int[][] items = new int[n][2];
        for (int i = 0; i < n; i++) {
    
    
            items[i][0] = input.nextInt();
            items[i][1] = input.nextInt();
            totalV += items[i][0];
            totalN += items[i][1];
        }
        input.close();
        if (totalV <= v) {
    
    
            System.out.println(totalN);
            return;
        }
        System.out.println(maxValue(n, v, items));
    }
}

解题代码3-Java:对于解题代码2的优化

import java.util.*;

public class Main {
    
    
    public static int N = 1010;

    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int v = input.nextInt();
        int[] dp = new int[N];
        for (int i = 1; i <= n; i++) {
    
    
            int vi = input.nextInt();
            int wi = input.nextInt();
            for (int j = v; j >= vi; j--) {
    
    
                dp[j] = Math.max(dp[j], dp[j - vi] + wi);
            }
        }
        System.out.println(dp[v]);
    }
}

猜你喜欢

转载自blog.csdn.net/HRT48267868/article/details/112727676
今日推荐