背包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
英文地址,請注明出處:http://en.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…