背包DP之多重背包

背包DP之多重背包

    • 一、多重背包基础认知
      • 1.1 问题定义
      • 1.2 核心特征
    • 二、基础解法:暴力拆分
      • 2.1 核心思路
      • 2.2 代码实现
      • 2.3 局限性分析
    • 三、优化解法:二进制拆分
      • 3.1 优化原理
      • 3.2 拆分步骤
      • 3.3 代码实现
      • 3.4 复杂度分析
    • 四、二进制拆分过程
    • 五、多重背包的变种与应用
      • 5.1 变种问题
      • 5.2 应用场景
      • 三种背包的区别
      • 总结

背包问题模型中多重背包是介于0/1背包(每种物品1个)和完全背包(每种物品无限个)之间的中间态——它允许每种物品选择有限次(如每种物品最多选3个),这种模型更贴近现实场景(如“商品限购”“物资有限”),但解法复杂度更高。

一、多重背包基础认知

1.1 问题定义

给定n种物品,每种物品有重量w[i]、价值v[i]和数量上限cnt[i];背包容量为C。每种物品最多选择cnt[i],求在总重量不超过C的前提下,能装入物品的最大总价值。

示例:

  • 输入:n=2, C=10, w=[3,4], v=[5,6], cnt=[2,2](物品0最多2个,物品1最多2个)
  • 输出:17(选择2个物品0(3×2=6重量,5×2=10价值)和1个物品1(4重量,6价值),总重量6+4=10,总价值10+6=16?最优应为2个物品1(4×2=8重量,6×2=12)+1个物品0(3重量,5)→ 总重量11>10;正确最优:2个物品0(6重量,10)+1个物品1(4重量,6)→ 总重量10,价值16?或1个物品0(3)+2个物品1(8)→ 总重量11>10,因此正确输出16)

1.2 核心特征

  • 数量限制:每种物品有明确的选择上限(cnt[i]),区别于0/1背包(cnt[i]=1)和完全背包(cnt[i]=+∞)。
  • 容量约束:总重量≤C,与其他背包模型一致。
  • 优化目标:在数量和容量双重约束下最大化总价值。

多重背包的本质是“带数量和容量双重约束的物品选择优化”,其基础解法可通过0/1背包扩展得到,但直接实现效率较低,需通过“二进制拆分”优化。

二、基础解法:暴力拆分

2.1 核心思路

多重背包可直接转化为0/1背包:将每种物品按数量上限cnt[i]拆分为cnt[i]个独立物品(每个物品只能选1次),然后用0/1背包的解法求解。

例如:物品0(w=3, v=5, cnt=2)可拆分为2个相同物品:(3,5)(3,5),后续按0/1背包处理(每个物品最多选1次)。

2.2 代码实现

public class MultipleKnapsack {/*** 多重背包基础实现(暴力拆分)* @param n 物品种类数* @param C 背包容量* @param w 重量数组* @param v 价值数组* @param cnt 数量上限数组* @return 最大价值*/public int maxValue(int n, int C, int[] w, int[] v, int[] cnt) {// 步骤1:暴力拆分物品(转化为0/1背包物品列表)List<Integer> newW = new ArrayList<>();List<Integer> newV = new ArrayList<>();for (int i = 0; i < n; i++) {// 将第i种物品拆分为cnt[i]个for (int k = 0; k < cnt[i]; k++) {newW.add(w[i]);newV.add(v[i]);}}// 步骤2:用0/1背包求解int[] dp = new int[C + 1];for (int i = 0; i < newW.size(); i++) {int currW = newW.get(i);int currV = newV.get(i);// 0/1背包逆序遍历for (int j = C; j >= currW; j--) {dp[j] = Math.max(dp[j], dp[j - currW] + currV);}}return dp[C];}public static void main(String[] args) {MultipleKnapsack solver = new MultipleKnapsack();int n = 2;int C = 10;int[] w = {3, 4};int[] v = {5, 6};int[] cnt = {2, 2};System.out.println(solver.maxValue(n, C, w, v, cnt)); // 输出16}
}

2.3 局限性分析

  • 时间复杂度:设物品总数量为N = sum(cnt[i]),则复杂度为O(N×C)。当cnt[i]较大(如cnt[i]=1000)时,N可达10^6C=10^4时总操作次数为10^10,严重超时。
  • 空间复杂度:拆分后物品列表需存储N个物品,内存占用大。

因此,暴力拆分仅适用于cnt[i]较小的场景(如cnt[i]≤10),需更高效的优化方法。

三、优化解法:二进制拆分

3.1 优化原理

二进制拆分的核心是“用少量物品组合表示所有可能的选择次数”,避免暴力拆分的冗余。

例如:cnt=5(最多选5个),可拆分为1、2、21+2+2=5):

  • 选0个:不选任何拆分物品;
  • 选1个:选1
  • 选2个:选2
  • 选3个:选1+2
  • 选4个:选2+2
  • 选5个:选1+2+2

通过2^0, 2^1, ..., 2^k, rr为剩余数量)的拆分方式,可覆盖0~cnt的所有选择次数,且拆分后物品数从cnt降至log2(cnt)(如cnt=1000仅需10个拆分物品)。

3.2 拆分步骤

对每种物品i(数量cnt[i]):

  1. 初始化k=1(从2^0开始);
  2. k ≤ cnt[i],则拆分出一个重量k×w[i]、价值k×v[i]的物品,cnt[i] -= kk *= 2
  3. cnt[i] > 0,则拆分出一个重量cnt[i]×w[i]、价值cnt[i]×v[i]的物品;
  4. 拆分后的物品按0/1背包处理(每个拆分物品最多选1次)。

3.3 代码实现

public class MultipleKnapsackOptimized {/*** 多重背包优化实现(二进制拆分)* @param n 物品种类数* @param C 背包容量* @param w 重量数组* @param v 价值数组* @param cnt 数量上限数组* @return 最大价值*/public int maxValue(int n, int C, int[] w, int[] v, int[] cnt) {// 步骤1:二进制拆分物品List<Integer> newW = new ArrayList<>();List<Integer> newV = new ArrayList<>();for (int i = 0; i < n; i++) {int currW = w[i];int currV = v[i];int currCnt = cnt[i];// 二进制拆分:k=1,2,4,...for (int k = 1; k <= currCnt; k *= 2) {newW.add(k * currW);newV.add(k * currV);currCnt -= k;}// 剩余数量拆分if (currCnt > 0) {newW.add(currCnt * currW);newV.add(currCnt * currV);}}// 步骤2:0/1背包求解int[] dp = new int[C + 1];for (int i = 0; i < newW.size(); i++) {int currW = newW.get(i);int currV = newV.get(i);for (int j = C; j >= currW; j--) {dp[j] = Math.max(dp[j], dp[j - currW] + currV);}}return dp[C];}public static void main(String[] args) {MultipleKnapsackOptimized solver = new MultipleKnapsackOptimized();int n = 2;int C = 10;int[] w = {3, 4};int[] v = {5, 6};int[] cnt = {2, 2};System.out.println(solver.maxValue(n, C, w, v, cnt)); // 输出16}
}

3.4 复杂度分析

  • 时间复杂度:拆分后物品总数N' = sum(log2(cnt[i])),若cnt[i]≤1000n=100,则N'≈100×10=1000C=10^4时总操作次数为1000×10^4=10^7,效率大幅提升。
  • 空间复杂度O(N' + C)N'远小于暴力拆分的N

二进制拆分是多重背包的标准优化方法,适用于cnt[i]较大的场景(cnt[i]≤10^5均可处理)。

四、二进制拆分过程

以示例中“物品0(w=3, v=5, cnt=2)”为例:

  1. 拆分cnt=2
    • k=11 ≤ 2,拆分出(1×3, 1×5)=(3,5)currCnt=2-1=1
    • k=22 > 1,停止循环;
    • 剩余currCnt=1,拆分出(1×3, 1×5)=(3,5)
    • 拆分后物品:[(3,5), (3,5)](与暴力拆分相同,因cnt较小)。

以“物品(cnt=5)”为例:

  1. 拆分cnt=5
    • k=1:拆分(1w, 1v)currCnt=5-1=4
    • k=2:拆分(2w, 2v)currCnt=4-2=2
    • k=44 > 2,停止循环;
    • 剩余currCnt=2:拆分(2w, 2v)
    • 拆分后物品:(1w,1v), (2w,2v), (2w,2v)(3个物品,覆盖0~5所有选择)。

五、多重背包的变种与应用

5.1 变种问题

  1. 混合背包(0/1+完全+多重)

    • 问题:物品可能是0/1(cnt=1)、完全(cnt=+∞)或多重(1<cnt<+∞)。
    • 解法:分类处理——0/1直接加、完全按正序、多重二进制拆分后按0/1处理。
  2. 二维多重背包(重量+体积约束)

    • 问题:物品有重量和体积两个约束,需同时满足。
    • 解法:用二维DP数组dp[j][k]j为重量,k为体积),拆分后按二维0/1背包处理。
  3. 数量下限约束

    • 问题:每种物品至少选minCnt[i]个,最多选maxCnt[i]个。
    • 解法:先强制选minCnt[i]个(扣除容量和价值),剩余数量按maxCnt[i]-minCnt[i]的多重背包处理。

5.2 应用场景

  • 商品限购:如“每人最多买3件”,用多重背包选择最优组合。
  • 物资分配:如“仓库有5台设备,每台重量10,价值20”,有限运力下最大化运输价值。
  • 资源打包:如“零件按包出售(每包2个),最多买4包”,转化为多重背包(cnt=4,每包视为拆分后的物品)。

三种背包的区别

模型物品选择次数核心解法时间复杂度(优化后)
0/1背包最多1次逆序遍历容量O(n×C)
完全背包无限次正序遍历容量O(n×C)
多重背包最多cnt[i]二进制拆分+0/1背包O(sum(log cnt[i]) × C)

总结

多重背包的核心是“数量限制下的物品选择”,其解法的关键是通过“二进制拆分”将问题高效转化为0/1背包,避免暴力拆分的冗余:

  1. 基础解法:暴力拆分为0/1背包,仅适用于cnt[i]较小的场景。
  2. 优化解法:二进制拆分通过2^k组合覆盖所有选择次数,将复杂度从O(N×C)降至O(sum(log cnt[i])×C)
  3. 变种迁移:混合背包、二维约束等变种可通过分类处理和维度扩展解决。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90875.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90875.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible 变量指南:声明、优先级、作用域与最佳实践(一)

Ansible 变量的声明 前言 全面理解 Ansible 变量是编写高效、可维护 Playbook 的关键。由于最近使用 Ansible 比较多&#xff0c;在变量问题上踩了不少坑&#xff0c;也因此对变量的声明&#xff0c;优先级和作用域有了更深的理解。姑且总结一下&#xff0c;分享给大家&#…

[极客大挑战 2019]FinalSQL--布尔盲注

直接看题可以看到题目给了提示盲注&#xff01;那么接下来就是寻找注入点了&#xff01;那么不能发现注入点就是id了&#xff01;注入类型为数值型注入&#xff01;这里直接尝试盲注。但是这里and被过滤了&&也不行。问了几个师傅说用or&#xff0c;但是空格被过滤了&am…

再谈fpga开发(状态机的应用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】前面说过&#xff0c;fpga上面最基础的部分是寄存器&#xff0c;而所有寄存器存在每一个clock下&#xff0c;都有被翻转的可能性。至于这些寄存器是…

TCP如何解决网络切换问题

一、传统TCP的网络切换问题核心问题&#xff1a;TCP 连接基于四元组&#xff08;源IP、源端口、目的IP、目的端口&#xff09;&#xff0c;IP 变化导致连接失效二、改进方案与技术演进1. MPTCP&#xff08;多路径TCP&#xff09; - 主流解决方案核心机制&#xff1a;单连接多路…

【Linux】常用命令(一)

【Linux】常用命令 一1. ls1.1 ls -a 显示所有文件及其目录1.2 ls -A 不显示当前目录和父目录1.3 ls -d 显示目录本身&#xff0c;而不是显示其内部内容1.4 ls -i 显示文件的inode属性信息1.4.1 实际用途场景1.5 ls -l 显示文件的详细属性信息1.6 ls -R 递归显示所有子文件1.7 …

Window 部署 coze-stdio(coze 开发平台)

参考链接 https://github.com/coze-dev/coze-studio/wiki/2.-%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B https://github.com/coze-dev/coze-studio/wiki/3.-%E6%A8%A1%E5%9E%8B%E9%85%8D%E7%BD%AE 环境说明 Docker&#xff1a;28.3.2 系统&#xff1a;Window 11 配置要求 CP…

【Git】Git LFS的使用

一、简介 Git LFS&#xff08;Git Large File Storage&#xff09;是由 GitHub 开发的一款 Git 扩展工具&#xff0c;旨在帮助开发者更高效地管理仓库中的大文件。传统 Git 会将文件的每个版本完整存储在仓库历史中&#xff0c;导致大文件&#xff08;如音频、视频、数据集、二…

不坑盒子:Word里1秒制作“花括号”题目,多音字组词、形近字组词……

1. 30秒看懂它能干啥 用“不坑盒子”插件&#xff0c;在 Word 里输入&#xff1a; 乐,l(快乐),yu(音乐);长,chng(长短),zhǎng(长大)点一下【总分关系】&#xff0c;瞬间出现左边是“乐”右边并列两行拼音括号的花括号结构&#xff1b;再点【并列关系】&#xff0c;又能做出只…

Gateway网关层灰度方案—xx互联网医院系统灰度发布设计与思路详解

通过之前技术的积累&#xff0c;终于开始了本文的编写&#xff0c;如果对灰度、负载均衡、上下文传递、网关不太理解&#xff0c;可以先学习博主的以下博客内容。共勉&#xff1a; 企业级 Java 应用灰度发布设计方案与实践全解析《Spring 中上下文传递的那些事儿》 Part 1&…

学习游戏制作记录(改进投掷剑的行为)7.27

1.实现剑跟随飞行方向旋转修改剑的预制体使剑的朝向对准右x轴Sword_Skill_Contorl脚本&#xff1a;private void Update(){transform.right rb.velocity;//时刻更新位置}2.实现剑插入地面或者敌人修改预制体为触发器Sword_Skill_Contorl脚本&#xff1a;private bool canRotat…

嵌入式软件面试八股文

目录 一、指针函数和函数指针 二、指针的大小 三、sizeof 和 strlen 区别 四、数组指针和指针数组 五、C语言里面内存分配的方式 六、struct结构体和union联合体的区别 八、数组和链表的区别 九、写一个宏这个红返回输入参数比较小的一个 十&#xff0c;使用#include<…

Gradle#Plugin

查看任务来自那个插件 /gradlew tasks --all <taskName>Java Plugin Java Library Plugin

渗透高级-----测试复现(第三次作业)

文章目录测试复现一&#xff0c;环境搭建二&#xff0c;通过VS Code连接cacti三&#xff0c;测试测试复现 一&#xff0c;环境搭建 1&#xff0c;在ubuntu虚拟机上安装MySql数据库&#xff1a; apt-get upgrade # 更新apt-get upgrade apt-get update # 更新apt-ge…

LINUX727 磁盘管理回顾1;配置文件回顾

逻辑卷快照 快照为什么这么小RAID 磁盘阵列 raid 0 raid 1 raid5 raid10raid0 raid1 raid5 raid6 raid10 rank;create raid0 mdadm -c /dev/md0 -l 0 -n 2 /dev/sdb3 /dev/sdb4 raid1 mdadm -c /dev/md1 -l 1 -n 2 /dev/sdb5 /dev/sdb6 raid5 mdadm -c /dev/md5 -l 5 -n 3 -x …

【笔记】Einstein关系式 D = ukBT 的推导与应用研究

文章目录从涨落理论和能量均分定理的数学推导基于平衡统计力学的推导1. 漂移流的来源&#xff1a;Jdrift−μρ∇UJ_{drift} -μρ∇UJdrift​−μρ∇U物理机制粒子流的形成2. 扩散流的来源&#xff1a;Jdiffusion−D∇ρJ_{diffusion} -D∇ρJdiffusion​−D∇ρ3. 热平衡要…

AJAX 原理_第一节_XHR 对象

文章目录1.AJAX原理1.1 初识XML1.2 查询参数1.3 案例-地区查询1.4 案例-注册-设置请求头1.AJAX原理 1.1 初识XML AJAX原理是什么? XMLHttpRequest对象 XHR对象定义: 通过XMLHttpRequest可以在不刷新页面的情况下请求特定URL,获取数据.这允许页面在不影响用户操作的情况下,更…

BeautifulSoup 使用详解与实战示例

BeautifulSoup 是一个用于解析HTML和XML文档的Python库&#xff0c;它能够将复杂的HTML文档转换成一个复杂的树形结构&#xff0c;使得我们可以轻松地查找和提取所需的内容。下面我将详细介绍BeautifulSoup的使用流程&#xff0c;并结合实际示例进行说明。一、安装与基础使用1.…

LangChain实战——实现多轮对话 + Function Calling

随着大语言模型&#xff08;LLMs&#xff09;的迅猛发展&#xff0c;“Function Calling”&#xff08;函数调用&#xff09;逐渐成为一个重要的能力&#xff0c;它使得模型不仅能聊天&#xff0c;还能像“中控大脑”一样调用外部函数完成具体任务&#xff0c;比如查天气、调用…

湖南(源点咨询)市场调研 如何在行业研究中快速有效介入 起头篇

行业研究从业人员经常需要在承接研究案子后快速的摸清委托方所在行业。而俗话说&#xff0c;隔行如隔山&#xff0c;快速了解行业&#xff0c;主要用于行业分析报告及为市场细分准入进行前期铺垫&#xff0c;要想摸清一个行业&#xff0c;需要长期持续的跟踪。了解一个行业&…

【c++】从 “勉强能用” 到 “真正好用”:中文问答系统的 200 行关键优化——关于我用AI编写了一个聊天机器人……(16)

先看核心结论&#xff1a;两段代码的本质区别如果用一句话总结两段代码的差异&#xff1a;前者是 “带中文支持的问答系统”&#xff0c;后者是 “真正适配中文的问答系统”。具体来说&#xff0c;两段代码的核心功能都是 “加载问答数据→接收用户输入→匹配答案”&#xff0c…