c++背包問題


又鴿了好久……

前言

博主剛剛學會背包問題不久,然后有一段時間沒練習了

今天就來重新溫習一下,順手就寫了這一篇博客。

好了,下面進入正題!

算法簡介

背包問題是動態規划的一個分支

主要是分成了01背包、完全背包和多重背包。

下面從01背包開始講解。

背包算法介紹

01背包

基本概念

01背包是在M件物品取出若干件放在空間為W的背包里,每件物品的體積為W1,W2至Wn,與之相對應的價值為P1,P2至Pn。01背包可謂是背包問題中最簡單的問題。01背包的約束條件是給定幾種物品,每種物品有且只有一個,並且有權值和體積兩個屬性。在01背包問題中,因為每種物品只有一個,對於每個物品只需要考慮選與不選兩種情況。如果不選擇將其放入背包中,則不需要處理。如果選擇將其放入背包中,由於不清楚之前放入的物品占據了多大的空間,需要枚舉將這個物品放入背包后可能占據背包空間的所有情況。

問題雛形

有N件物品和一個容量為V的背包。第i件物品的體積是c[i],價值是w[i]。求解將哪些物品裝入背包可使價值總和最大。

問題解答

了解了基本概念和問題雛形后我們就可以來想做題的方法了。

從題目里看,我們就能看出,01背包有個特點:每種物品僅有一件,可以選擇放或不放。

所以我們就可以把每種情況都枚舉一遍。

首先建立一個二維數組dp[][]表示價值,w[i]是每件物品的價值,c[i]是每件物品的體積

然后就想,由於它只有放和不放兩種狀態,我們就要比較這兩種狀態的價值,用max函數。

狀態轉移方程:dp[i][v]=max{dp[i-1][v],dp[i-1][v-c[i]]+w[i]}

其中,dp[i-1][v]表示不放該物品,dp[i-1][v-c[i]]+w[i]表示放入該物品。

這樣做一個循環枚舉各種情況即可。

for (i = 1; i <= n; i++)
    for (j = v; j >= c[i]; j--)//在這里,背包放入物品后,容量不斷的減少,直到再也放不進了
        dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]);

最后的結果就是最大值。

還有一些優化的方法:01滾動就地滾動

01滾動:

我們可以看到每一行的結果實際上只與上一行有關,所以就可以01滾動——f[i][0,1] 一行記錄前一行的值,另一行記錄當前行的值……

所以,這是一種簡化的好辦法!

就地滾動:

就地滾動就是用一個一維數組,把之前的狀態和當前的狀態放在同一個數組,但是在寫的過程中會有問題。

先上代碼吧:

for(i=1 ; i<= n ; i++)
     for(j= c[i]; j<v ; j++)     
        if(!dp[j-c[i]) dp[j] = dp[j-c[i]];

我們會發現,這樣的話一個物品會被重復計算多次。

實戰演練

問題1:采葯

這是一個經典的問題哦!

飛機場:洛谷P1048 采葯

問題解答(不可用於直接AC本題,可進行參考!)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<algorithm>
 6 #define inf 100000000
 7 //狀態:dp[i][j]表示考慮前i個草葯,目前體積之和為j,可以獲得的最大價值
 8 //轉移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) 
 9 //答案:dp[m][max(...)]
10 //不需要初始化,直接算 
11 //復雜度:O(m*t) 
12 int dp[105][1005],w[105],v[105];
13 using namespace std;
14 int main()
15 {
16     int t,m;
17     cin>>t>>m;
18     for(int i=1;i<=m;i++)
19     {
20         cin>>w[i]>>v[i];
21     }
22     for(int i=1;i<=m;i++)
23         for(int j=t;j>=0;j--)
24         {
25             if(j-w[i]>=0)
26                 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
27             else
28                 dp[i][j]=dp[i-1][j];
29         }
30     int ans=0;
31     for(int i=0;i<=t;i++)
32     {
33         ans=max(dp[m][i],dp[m][i-1]);
34     }
35     cout<<ans;
36     return 0;
37 }

 當然還有一種做法。可以直接使用一維數組:參見一本通。

 完全背包

基本概念

這個問題非常類似於01背包問題,所不同的是每種物品有無限件,也就是從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取0件、取1件、取2件……取[V/c]件等很多種。

問題雛形

 有 N 種物品和一個容量為 V 的背包,每種物品都有無限件可用。放入第 i 種物品的費用是 Ci ,價值是 Wi 。求解:將哪些物品裝入背包,可使這些物品的耗費的費用總和不超過背包容量,且價值總和最大。

問題解答

這個題目有一個和01背包不一樣的地方:每種物品有無數件!

然后我們想前面我說過的就地滾動,會計算多次,這不正巧?

實戰演練

問題2:瘋狂的采葯

這個……這個題目的介紹不大正經,未成年人請在家長的陪伴下觀看。

飛機場:洛谷P1616 瘋狂的采葯

問題解答(不可用於直接AC本題,可進行參考!)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<algorithm>
 6 #define inf 100000000
 7 //狀態:dp[i][j]表示考慮前i個草葯,目前體積之和為j,可以獲得的最大價值
 8 //轉移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) 
 9 //答案:dp[m][max(...)]
10 //不需要初始化,直接算 
11 //復雜度:O(m*t) 
12 int f[1000001],t,m,ti,v;
13 using namespace std;
14 int main()
15 {
16     cin>>t>>m;
17     for(int i=1;i<=m;i++)
18     {
19         cin>>ti>>v;
20         for(int j=ti;j<=t;j++)
21             f[j]=max(f[j],f[j-ti]+v);
22     }
23     cout<<f[t];
24     return 0;
25 }

 多重背包

基本概念&問題雛形

有N種物品和一個容量為V的背包。第i種物品最多有n[i]件可用,每件體積是w[i],價值是v[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。

問題解答

這個問題的特點是:每種物品有一定數量

這個運用的是二進制思想

轉化為01背包求解:把第i種物品換成n[i]件01背包中的物品,則得到了物品數為Σn[i]的01背包問題。

我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換以后的物品。

另外,取超過n[i]件的策略必不能出現。 方法是:將第i種物品分成若干件物品,其中每件物品有一個系數,這件物品的費用和價值均是原來的費用和價值乘以這個系數。

使這些系數分別為1,2,4,...,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數。例如,如果n[i]為13,就將這種物品分成系數分別為1,2,4,6的四件物品。

解決問題的道理

1) 1+2+4+...+2^(k-1)+n[i]-2^k+1 = n[i]   這就保證了最多為n[i]個物品

2)1,2,4,……,2^(k-1),可以湊出1到2^k – 1的所有整數(聯系一個數的二進制拆分即可證明,證明過程在下面的題解中)

3) 2^k……n[i]的所有整數可以用若干個上述元素湊出(可以理解為湊n[i]-t, 而n[i]為上面所有數的和,t則是一個小於2^k 的數,那么在所有的數中去掉組成2^k 的那些數剩下的就可以組成n[i]-t了)

當然,這個二進制的道理我在之前的一篇博客上寫過,一會的實戰演練會帶你們去。

實戰演練

問題3:寶物篩選

這是一道很水的藍題……

飛機場:洛谷P1776 寶物篩選題解(我寫的,你們還有更詳細的多重背包解決思路)


后記

本文就寫這么多了,背包問題在考試中會經常出現,希望大家深入理解其中的思想。

 

 

 

客官,給個贊再走唄?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM