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

01背包问题

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

题目描述

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

输入格式

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

输出格式

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

数据范围

0 < \(N\),\(V\) ≤ 1000
0 < \(v_i\),\(w_i\) ≤ 1000学习

样例输入

4 5
1 2
2 4
3 4
4 5

样例输出

8

解题思路1:暴力破解

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

解题代码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:初始网格spa

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

在每一个一格子你都要作出一个选择:放不放进这一行对应的物品。
物品1所占空间为 \(1\) ,也就是说物品1能放进容量为 \(1\) 的这个背包,所以这个单元格包含物品1。因此往第一行第一列中装入物品1,价值为2。
图2:填充第一行第一列
这行的其余单元格也同样。别忘了,这是第一行,只有一个物品可供你选择,换而言之,你伪装如今尚未打算放进其余物品。
填充完以后以下图:
图3:填充完第一行
此时你极可能心存疑惑:原题说的是容量为5的背包,咱们为什么要考虑容量为一、二、三、4的背包呢?动态规划从子问题着手,逐步解决大问题。这里解决的子问题将帮助你解决大问题。
这行表示的是当前的最大价值,并非最终解。随着算法往下执行,将逐步修改最大价值。.net

2、接下来看第二行。

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

3、作完第二行,接下来看第三行

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

4、第四行也同样

图7:填充完第四行

5、最终结果

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

6、程序实现

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

  • 若是将\(i\)放入,\(当前背包的最大价值 = 第i号物品的价值 + 出去i号物品占用空间后剩余的空间所能存放的最大价值\)
    即:\(valueWith\_i = w_i + dp[i-1][j-v_i];\)
  • 若是不放入\(i\)\(当前背包的价值 = 前i-1个物品存放在背包中的最大价值\)
    即:\(valueWithout\_i = dp[i-1][j];\)
  • 最终,\(dp[i][j]\)的结果取二者的较大值。
    即:\(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));
    }
}
相关文章
相关标签/搜索