# 01 背包問題 ## 問題描述: 有n件物品和一個容量為W的背包。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入背包可使價值總和最大。 ## 解: 直接用例子說明: 5個物品,(重量,價值)分別為:(5,12),(4,3),(7,10),(2,3),(6,6) | 背包容量 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | | -------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 物品1+~5 | 0 | 0| 0 |0|0| 0| 6| 12 | 12| 15|15 | 18 | 22 | 22|25 | 25 | | 物品1+~4 |0| 0 |3 | 3| 3 | 3 | 3| 12|12 |15 |15| 18 | 22 | 22 | 25 |25 | | 物品1+2+3 | 0| 0 | 0| 0 |0 |0 |0 |12 | 12 | 15 | 15 | 15 |22 | 22| 22 |22 | | 物品1+2 |0|0 | 0 | 0 | 3 |12 | 12|12 |12 | 15| 15| 15| 15 | 15 | 15 |15 | | 物品1 |0|0|0|0|0|12|12|12|12|12|12|12|12|12|12|12| ``` for(int i = 1; i <= n; i++){ for(int j = 0; j <= W; j++){ if(j < w[i]) dp[i][j] = dp[i-1][j]; //沒辦法放,物品重量超過背包容量 else dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]); // max(不放物品i , 放物品i); } } ``` 優化空間複雜度: 以上方法的時間和空間複雜度均為O(VN),其中時間複雜度已經不能再優化了,但空間複雜度卻可以優化到O(V)。因為其實根本不需要存二維陣列,只有最上面那一列是重要的。例如:放入物品1,2,3之後,物品1,物品1+2這兩列便可捨棄。這種二維轉一維的儲存方式稱為滾動數組。 ``` for(int i = 1; i <= n; i++){ for(int j = W; j >= w[i]; j--) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); // max(不放i, 放i); } ``` 初始化: 若要求恰好裝滿背包,那麼在初始化時除了f[0]為0外其它f[1..V]均設為-∞,這樣就可以保證最終得到的f[N]是一種恰好裝滿背包的最優解。 如果並沒有要求必須把背包裝滿,而是只希望價格盡量大,初始化時應該將f[0..V]全部設為0。因為什麼都不放也是一種放法。 ## NCTU 484-Seesaw description: 給一串數字,將數字分成兩組,使兩組總和差距最小。求最小差距為何。 solve: 想像成欲填滿總和一半重量的背包。再將總和扣掉能填滿的最大重量*2(此題價值和重量視為相等)。 ``` #include<bits/stdc++.h> using namespace std; int dp[10001]; int main(){ ios_base::sync_with_stdio(false); cin.tie(0); memset(dp,0,sizeof(dp)); int n; cin>>n; int sum=0; vector<int> vec(n+1); for(int i=1;i<=n;++i){ int x; cin>>x; sum+=x; vec[i]=x; } int target=sum/2; for(int i=1;i<=n;++i){ for(int j=target;j>=vec[i];j--){ dp[j]=max(dp[j],dp[j-vec[i]]+vec[i]); } } cout<<sum-dp[target]*2<<endl; return 0; } ``` ## ps: 另有fractional背包問題,可直接用greedy解(從最大價值的開始拿)。