c++揹包問題

又鴿了很久……html

前言

博主剛剛學會揹包問題不久,而後有一段時間沒練習了ios

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

好了,下面進入正題!數組

算法簡介

揹包問題是動態規劃的一個分支函數

主要是分紅了01揹包、徹底揹包和多重揹包。優化

下面從01揹包開始講解。spa

揹包算法介紹

01揹包

基本概念

01揹包是在M件物品取出若干件放在空間爲W的揹包裏,每件物品的體積爲W1,W2至Wn,與之相對應的價值爲P1,P2至Pn。01揹包可謂是揹包問題中最簡單的問題。01揹包的約束條件是給定幾種物品,每種物品有且只有一個,而且有權值和體積兩個屬性。在01揹包問題中,由於每種物品只有一個,對於每一個物品只須要考慮選與不選兩種狀況。若是不選擇將其放入揹包中,則不須要處理。若是選擇將其放入揹包中,因爲不清楚以前放入的物品佔據了多大的空間,須要枚舉將這個物品放入揹包後可能佔據揹包空間的全部狀況。code

問題雛形

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

問題解答

瞭解了基本概念和問題雛形後咱們就能夠來想作題的方法了。blog

從題目裏看,咱們就能看出,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 寶物篩選題解(我寫的,大家還有更詳細的多重揹包解決思路)


後記

本文就寫這麼多了,揹包問題在考試中會常常出現,但願你們深刻理解其中的思想。

 

 

 

客官,給個贊再走唄?

相關文章
相關標籤/搜索