背包问题(三)--多重背包

参考资料
背包九讲
https://www.acwing.com/activity/content/11/

多重背包模型

背包容量为V,有N件物品,每件物品的体积是vi,价值是wi,第i件物品最多有mi件可用。求将物品装入背包获得的最大价值

思路
f(i,v)表示前i件商品,背包容量不大于v时的最大价值
f(i,v) = max(f(i-1,v-kc[i])+kwi) 0<=k<=mi

代码实现

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int V = sc.nextInt();
        int[] v = new int[N + 1];
        int[] w = new int[N + 1];
        int[] s = new int[N + 1];
        for (int i = 1; i <= N; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
            s[i] = sc.nextInt();
        }

        int[][] dp = new int[N + 1][V + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = 0; j <= V; j++) {
                for (int k = 0; k <= s[i] && j >= k * v[i]; k++) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * v[i]] + k*w[i]);
                }
            }
        }
        System.out.println(dp[N][V]);
    }
}

复杂度优化
二进制优化:
通过数字1,2可以拼出1~3的任意数字
通过1,2,4可以拼出1~7的任意数字
通过1,2,4,8可以拼出1~15的任意数字
...
由此,对于第i件物品,如果有1023件,那我们可以用1,2,4,8,16,32,64,...,512拼出1023件物品
1,2,4,8,16,32...可以看成是01背包,要么选择1件商品,要么不选,...,要么选择32件商品,要么不选

本来想要凑出1~n个数需要O(n)的复杂度,现在只需要O(logn)的复杂度

总得来说,就是将每个物品分为logm份,然后做01背包。总得复杂度O(NVlogm)

import java.util.Scanner;

public class Main2 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int V = sc.nextInt();
        int[] v = new int[12010]; // N * log(每种物品最多的数量)
        int[] w = new int[12010];

        int cnt = 0;
        for (int i = 1; i <= N; i++) {
            int a = sc.nextInt(); // 第i种物品的体积
            int b = sc.nextInt(); // 第i种物品的价值
            int c = sc.nextInt(); // 第i种物品的数量

            int k = 1;
            while (k <= c) {
                cnt++;
                v[cnt] = a * k;
                w[cnt] = b * k;
                c -= k;
                k *= 2;
            }
            if (c > 0) {
                cnt++;
                v[cnt] = a * c;
                w[cnt] = b * c;
            }
        }

        // 01背包
        N = cnt;
        int[][] dp = new int[N + 1][V + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = 0; j <= V; j++) {
                dp[i][j] = dp[i - 1][j];
                if(j>=v[i]){
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
                }
            }
        }
        System.out.println(dp[N][V]);
    }
}

对上面的01背包优化


        // 01背包
        N = cnt;
        int[] dp = new int[V + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = V; j >= v[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
            }
        }
        System.out.println(dp[V]);

记录路径

leetcode798 得分最高的最小轮调

给定一个数组 A,我们可以将它按一个非负整数 K 进行轮调,这样可以使数组变为 A[K], 
A[K+1], A{K+2], ... A[A.length - 1], A[0], A[1], ..., A[K-1] 的形式。此后,任何值小于或等于其
索引的项都可以记作一分。

例如,如果数组为 [2, 4, 1, 3, 0],我们按 K = 2 进行轮调后,它将变成 [1, 3, 0, 2, 4]。这将记
作 3 分,因为 1 > 0 [no points], 3 > 1 [no points], 0 <= 2 [one point], 2 <= 3 [one point], 4 <= 4 
[one point]。

在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调索引 K。如果有多个答案,返
回满足条件的最小的索引 K。

示例 1:
输入:[2, 3, 1, 4, 0]
输出:3
解释:
下面列出了每个 K 的得分:
K = 0,  A = [2,3,1,4,0],    score 2
K = 1,  A = [3,1,4,0,2],    score 3
K = 2,  A = [1,4,0,2,3],    score 3
K = 3,  A = [4,0,2,3,1],    score 4
K = 4,  A = [0,2,3,1,4],    score 3
所以我们应当选择 K = 3,得分最高。
 

示例 2:
输入:[1, 3, 0, 2, 4]
输出:0
解释:
A 无论怎么变化总是有 3 分。
所以我们将选择最小的 K,即 0。

提示:
A 的长度最大为 20000。
A[i] 的取值范围是 [0, A.length]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/smallest-rotation-with-highest-score
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

leetcode799 香槟塔

我们把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个,依次类推到第100
层,每个玻璃杯(250ml)将盛有香槟。

从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流
量的流向左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯
子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)

例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各
自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒
第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香
槟,如下图所示。

现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例(i 
和 j都从0开始)。

示例 1:
输入: poured(倾倒香槟总杯数) = 1, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.0
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻
璃杯都是空的。

示例 2:
输入: poured(倾倒香槟总杯数) = 2, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.5
解释: 我们在顶层(下标是(0,0)倒了两杯香槟后,有一杯量的香槟将从顶层溢出,位于
(1,0)的玻璃杯和(1,1)的玻璃杯平分了这一杯香槟,所以每个玻璃杯有一半的香槟。
注意:

poured 的范围[0, 10 ^ 9]。
query_glass 和query_row 的范围 [0, 99]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/champagne-tower
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
image.png

你可能感兴趣的:(背包问题(三)--多重背包)