這段代碼實現了一個多重背包問題的動態規劃解法,并且使用了二進制拆分(或稱二進制優化)來優化物品的數量處理。這種方法可以顯著減少狀態轉移的次數,提高算法的效率。以下是代碼的詳細思路解析:
1. 問題背景
給定 n
個物品,每個物品有其體積 a
、價值 b
和數量 s
,以及一個容量為 m
的背包。目標是選擇物品使得總價值最大,同時總容量不超過背包的容量。與完全背包問題不同的是,多重背包問題中每個物品的數量是有限的。
2. 二進制拆分的概念
二進制拆分是一種優化技巧,用于處理多重背包問題中的物品數量。通過將每個物品的數量 s
拆分成若干個部分,每個部分的數量為 2^k
(k
從 0 開始),可以顯著減少狀態轉移的次數。例如,如果 s = 13
,可以拆分成 1 + 2 + 4 + 6
,其中 1 + 2 + 4 = 7
,剩余的 6
作為最后一部分。
3. 代碼邏輯解析
(1) 輸入數據
cin >> n >> m;
int cnt = 0;
for (int i = 1; i <= n; i++)
{int a, b, s;cin >> a >> b >> s;int k = 1;while (k <= s){cnt++;v[cnt] = a * k;w[cnt] = b * k;s -= k;k *= 2;}if (s > 0){cnt++;v[cnt] = a * s;w[cnt] = b * s;}
}
n = cnt;
-
用戶輸入物品數量
n
和背包容量m
。 -
對于每個物品,輸入其體積
a
、價值b
和數量s
。 -
使用二進制拆分將每個物品的數量
s
拆分成若干個部分,每個部分的數量為2^k
。 -
將每個部分作為一個新的物品,存儲到數組
v
和w
中。 -
更新物品總數
n
為拆分后的物品數量cnt
。
(2) 動態規劃狀態轉移
for (int i = 1; i <= n; i++)for (int j = m; j >= v[i]; j--)f[j] = max(f[j], f[j - v[i]] + w[i]);
-
外層循環:
-
遍歷每個物品,從第 1 個到第
n
個。
-
-
內層循環:
-
遍歷背包的每個容量,從
m
到v[i]
(逆序遍歷)。 -
逆序遍歷的原因是避免重復使用同一個物品。如果正序遍歷,同一個物品可能會被多次使用,從而變成完全背包問題。
-
-
狀態轉移:
-
f[j]
表示在容量為j
的背包下的最大價值。 -
不選擇第
i
個物品:f[j]
保持不變。 -
選擇第
i
個物品:如果當前容量j
大于等于第i
個物品的體積v[i]
,則可以考慮選擇第i
個物品,更新f[j]
為f[j - v[i]] + w[i]
,即在容量為j - v[i]
的背包下的最大價值加上第i
個物品的價值。
-
(3) 輸出結果
cout << f[m] << endl;
-
輸出最終的最大價值,即
f[m]
。
4. 代碼效率分析
-
時間復雜度:
-
二進制拆分將每個物品的數量
s
拆分成若干個部分,每個部分的數量為2^k
,因此每個物品最多被拆分成O(log s)
個部分。 -
動態規劃的狀態轉移時間復雜度為 O(n × m),其中
n
是拆分后的物品數量。 -
總時間復雜度為 O(n × m × log s)。
-
-
空間復雜度:
-
使用了一個一維數組
f
,空間復雜度為 O(m)。
-
5. 示例運行
輸入:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
輸出:
10
6. 總結
這段代碼的核心思路是通過動態規劃解決多重背包問題,并使用二進制拆分優化物品的數量處理。通過維護一個一維數組 f
,記錄不同狀態下的最大價值,并通過狀態轉移方程更新最大價值,最終找到在給定背包容量下的最大價值。這種方法的時間復雜度為 O(n × m × log s),空間復雜度為 O(m),適用于中等規模的多重背包問題。
完整代碼
#include<bits/stdc++.h>
using namespace std;// 定義常量 N 和 M,N 用于數組大小,M 這里未使用
const int N = 25000, M = 2010;
// n 表示物品的種類數,m 表示背包的容量
int n, m;
// v 數組存儲物品的體積,w 數組存儲物品的價值
int v[N], w[N];
// f 數組是一維數組,f[j] 表示背包容量為 j 時能獲得的最大價值
int f[N];int main()
{// 輸入物品的種類數 n 和背包的容量 mcin >> n >> m;// cnt 用于記錄經過二進制優化后物品的數量int cnt = 0;// 循環處理每種物品for(int i = 1; i <= n; i ++){// 輸入當前物品的體積 a、價值 b 和數量上限 sint a, b, s;cin >> a >> b >> s;// k 用于二進制拆分,初始為 1int k = 1;// 進行二進制拆分while(k <= s){// 物品數量加 1cnt ++;// 計算拆分后物品的體積v[cnt] = a * k;// 計算拆分后物品的價值w[cnt] = b * k;// 減去已拆分的數量s -= k;// k 乘以 2,繼續下一次拆分k *= 2;}// 如果還有剩余數量,將剩余部分作為一個新物品if(s > 0){cnt ++;v[cnt] = a * s;w[cnt] = b * s;}}// 更新物品的種類數為拆分后的數量n = cnt;// 使用 0 - 1 背包的方法進行動態規劃for(int i = 1; i <= n; i ++)// 內層循環從背包的最大容量 m 開始,遞減到當前物品的體積 v[i]for(int j = m; j >= v[i]; j --)// 比較不選擇第 i 個物品和選擇第 i 個物品兩種情況下的最大價值f[j] = max(f[j], f[j - v[i]] + w[i]);// 輸出背包容量為 m 時能獲得的最大價值cout << f[m] << endl;return 0;
}