题解 | #【模板】多重背包#

【模板】多重背包

https://www.nowcoder.com/practice/8fa10063d33a43dd9652c1511a34d461

题目链接

【模板】多重背包

题目描述

给定 种物品和一个容量为 的背包。第 种物品的体积是 ,价值是 ,并且数量最多有 件。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输入:

  • 第一行输入一个整数 ,表示有 组测试数据。
  • 对于每组测试数据:
    • 第一行输入两个整数 ,分别表示物品种类和背包容量。
    • 接下来 行,每行输入三个整数 ,分别表示第 种物品的体积、价值和数量。

输出:

  • 对于每组测试数据,输出一个整数,表示能获得的最大总价值。

解题思路

本题是典型的 多重背包问题。它介于01背包和完全背包之间:每种物品可以选择多次,但有数量上限。

  1. 朴素解法 (超时):

    • 最直接的想法是将每种物品看作 个独立的、相同的物品,然后用01背包的方法解决。但如果 很大,物品总数会非常多,导致超时。
  2. 二进制拆分优化:

    • 我们可以对物品数量 进行优化。核心思想是,任何一个在 范围内的整数,都可以由 以及一个余数 的和来表示。
    • 例如,如果一种物品有 件,我们可以将其拆分为4个新的“物品包”:
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
      • 数量为 的包:体积 ,价值
    • 通过选择这几个包的组合,我们可以凑出原物品数量从 的任意件。
    • 这样,我们将原来的一种物品(数量为 )拆分成了 个新的物品,每个新物品只能选一次。
  3. 转化为01背包:

    • 通过二进制拆分,我们将多重背包问题成功转化为了一个 01背包问题。新物品的总数是
    • 接下来,我们应用标准的01背包解法即可。
    • 状态定义: 表示容量为 的背包能获得的最大价值。
    • 状态转移: 对于每个拆分出的新物品(体积 , 价值 ),我们更新 数组:
    • 注意:01背包问题的内层循环(背包容量)必须 逆序遍历,以保证每个物品只被选择一次。

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct Item {
    int v, w;
};

int main() {
    int T;
    cin >> T;
    while (T--) {
        int N, V;
        cin >> N >> V;
        vector<Item> items;
        for (int i = 0; i < N; ++i) {
            int v, w, c;
            cin >> v >> w >> c;
            // 二进制拆分
            for (int k = 1; k <= c; k *= 2) {
                items.push_back({v * k, w * k});
                c -= k;
            }
            if (c > 0) {
                items.push_back({v * c, w * c});
            }
        }

        vector<long long> dp(V + 1, 0);
        
        // 01背包
        for (const auto& item : items) {
            for (int j = V; j >= item.v; --j) {
                dp[j] = max(dp[j], dp[j - item.v] + item.w);
            }
        }
        cout << dp[V] << "\n";
    }
    return 0;
}
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static class Item {
        int v, w;
        Item(int v, int w) {
            this.v = v;
            this.w = w;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while (T-- > 0) {
            int N = sc.nextInt();
            int V = sc.nextInt();
            List<Item> items = new ArrayList<>();
            for (int i = 0; i < N; i++) {
                int v = sc.nextInt();
                int w = sc.nextInt();
                int c = sc.nextInt();
                // 二进制拆分
                for (int k = 1; k <= c; k *= 2) {
                    items.add(new Item(v * k, w * k));
                    c -= k;
                }
                if (c > 0) {
                    items.add(new Item(v * c, w * c));
                }
            }

            long[] dp = new long[V + 1];

            // 01背包
            for (Item item : items) {
                for (int j = V; j >= item.v; j--) {
                    dp[j] = Math.max(dp[j], dp[j - item.v] + item.w);
                }
            }
            System.out.println(dp[V]);
        }
    }
}
T = int(input())
for _ in range(T):
    N, V = map(int, input().split())
    items = []
    for i in range(N):
        v, w, c = map(int, input().split())
        # 二进制拆分
        k = 1
        while k <= c:
            items.append((v * k, w * k))
            c -= k
            k *= 2
        if c > 0:
            items.append((v * c, w * c))

    dp = [0] * (V + 1)
    
    # 01背包
    for v_item, w_item in items:
        for j in range(V, v_item - 1, -1):
            dp[j] = max(dp[j], dp[j - v_item] + w_item)

    print(dp[V])

算法及复杂度

  • 算法:动态规划 (多重背包 + 二进制拆分优化)
  • 时间复杂度: - 为数据组数, 为背包容量, 为第 种物品的数量。
  • 空间复杂度: - 主要开销来自 数组和存储拆分后物品的列表。
全部评论

相关推荐

昨天 08:10
已编辑
门头沟学院 Java
2.4&nbsp;一面2.6&nbsp;二面2.9&nbsp;三面(hr面)2.13&nbsp;oc1.15号收到面试电话那会就开始准备,因为一开始没底所以选择推迟一段时间面试,之后开始准备八股,准备实习可能会问的东西,这期间hot100过了有六七遍,真的是做吐了快,八股也是背了忘,忘了背,面经也看了很多,虽然最后用上的只有几道题,可是谁知道会问什么呢自从大二上开始学java以来,一开始做外卖,点评,学微服务,大二下五六月时,开始投简历,哎,投了一千份了无音讯,开始怀疑自己(虽然能力确实很一般),后来去到一家小小厂,但是并不能学到什么东西,而且很多东西都很不规范,没待多久便离开,大二暑假基本上摆烂很怀疑自己,大三上因为某些原因开始继续学,期间也受到一俩个中小厂的offer,不过学校不知道为啥又不允许中小厂实习只允许大厂加上待遇不太好所以也没去,感觉自己后端能力很一般,于是便打算转战测开,学习了一些比较简单的测试理论(没有很深入的学),然后十二月又开始继续投,java和测开都投,不过好像并没有几个面试,有点打击不过并没有放弃心里还是想争一口气,一月初因为学校事比较多加上考试便有几天没有继续投,10号放假后便继续,想着放假应该很多人辞职可能机会大一点,直到接到字节的面试,心里挺激动的,总算有大厂面试了,虽然很开心,但同时压力也很大,心里真的很想很想很想进,一面前几天晚上都睡不好觉,基本上都是二三点睡六七点醒了,好在幸运终于眷顾我一次了(可能是之前太痛了),一面三十几分钟结束,问的都不太难,而且面试官人挺好但是有些问题问的很刁钻问到了测试的一些思想并不是理论,我不太了解这方面,但是也会给我讲一讲他的理解,但是面完很伤心觉得自己要挂了。但是幸运的是一面过了(感谢面试官),两天后二面,问的同样不算难,手撕也比较简单,但也有一两个没答出来,面试官人很好并没有追问,因为是周五进行的二面,没有立即出结果,等到周一才通知到过了,很煎熬的两天,根本睡不好,好在下周一终于通知二面过了(感谢面试官),然后约第二天三面,听别的字节同学说hr面基本上是谈薪资了,但是我的并不是,hr还问了业务相关的问题,不过问的比较浅,hr还问我好像比较紧张,而且hr明确说了还要比较一下,我说我有几家的面试都拒了就在等字节的面试,三面完后就开始等结果,这几天干啥都没什么劲,等的好煎熬,终于13号下午接到了电话通知oc了,正式邮件也同时发了,接到以后真的不敢信,很激动但更重要的是可以松一口气了,可以安心的休息一下了终于可以带着个好消息过年了,找实习也可以稍微告一段落了,虽然本人很菜,但是感谢字节收留,成为忠诚的节孝子了因为问的比较简单,面经就挑几个记得的写一下一面:1.实习项目的难点说一下2.实习中用到了哪些测试方法3.针对抖音评论设计一下测试用例4.手撕:合并两个有序数组二面:1.为什么转测开2.线程进程区别,什么场景适合用哪个3.发送一个朋友圈,从发出到别人看到,从数据流转的角度说一下会经历哪些过程4.针对抖音刷到广告视频设计测试用例5.手撕:无重复字符的最长字串
牛客85811352...:测开问这么简单?
查看8道真题和解析
点赞 评论 收藏
分享
02-07 10:52
复旦大学 Java
混子不想混:非常能理解,感觉他们就靠着入行早,打压新人一样。我这个公司也是,天天干的累死累活,然后绩效打C,合着让新人被绩效,像是年底攒棺材本一样。总是打击之后,还会让人开始自我怀疑,是不是我努力的还不够,实际上并不是,就是他们不做人,故意打压新人。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务