揹包問題-筆記整理

在這裏插入圖片描述

1.背景:

1.1 什麼是揹包問題:

揹包問題指這樣一類問題,題意往往可以抽象成:給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內,我們如何選擇,才能使得物品的總價格最高。(來自百度百科)

1.2 揹包問題的種類:

就ACM或者其它算法競賽而言,揹包問題可以分爲8種類型,其中最基礎的是0/1揹包問題。作爲動態規劃的典型問題,其狀態轉移方程往往需要認真理解並能自行推出。這八種問題分別爲:0/1揹包問題、完全揹包問題、多重揹包問題、混合三種揹包問題、二維費用揹包問題、分組揹包問題、有依賴的揹包問題、求揹包問題的方案總數。

2.0/1揹包問題

2.1 問題描述:

有N件物品和一個容量爲V的揹包。第i件物品的費用(即體積,下同)是w[i],價值是val[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

2.2 解題思路:

用動態規劃的思路,階段就是“物品的件數”,狀態就是“揹包剩下的容量”,那麼很顯然f [ i , v ] 就設爲從前 i 件物品中選擇放入容量爲 v 的揹包最大的價值。那麼狀態轉移方程爲:

f[i][v]=max{ f[i-1][v],f[i-1][v-w[i]]+val[i] }

這個方程可以如下解釋:只考慮子問題“將前 i 個物品放入容量爲 v 的揹包中的最大價值”那麼考慮如果不放入 i ,最大價值就和 i 無關,就是 f[ i - 1 ][ v ] , 如果放入第 i 個物品,價值就是 f[ i - 1][ v - w[i] ] + val[ i ],我們只需取最大值即可。

2.3 空間優化:

上述狀態表示,我們需要用二維數組,但事實上我們只需要一維的滾動數組就可以遞推出最終答案。考慮到用f[ v ]來保存每層遞歸的值,由於我們求f[ i ][ v ] 的時候需要用到的是f[ i-1 ][ v] 和 f[ i-1 ][v - w[i] ] 於是可以知道,只要我們在求f[ v ]時不覆蓋f[ v - w[i] ],那麼就可以不斷遞推至所求答案。所以我們採取倒序循環,即v = m(m爲揹包總容積)僞代碼如下:

  for i = 1..N
   for v = V..0
     f[ v ] = max{ f[ v ],f[ v-w[i] ]+val[ i ] };

2.4 代碼模板:(根據2.1問題作答)

#include
#include
#include
using namespace std;
const int maxn = 1e4;
int f[maxn];
int w[maxn],val[maxn];
void solve(int n,int m){
memset(f,0,sizeof f);
for(int i = 1;i <= n;i++){
for(int v = m;v > 0;v–){
if(v >= w[i])
f[v] = max(f[v],f[v-w[i]]+val[i]);
}
}
printf("%d\n",f[m]);
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m) != EOF){
for(int i = 1;i <= n;i++) scanf("%d%d",w+i,val+i);
solve(n,m);
}
return 0;
}

3.完全揹包問題

3.1 問題描述:

有N種物品和一個容量爲V的揹包,每種物品都有無限件可用。第i種物品的費用是w[i],價值是val[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

3.2 解題思路:

完全揹包問題與0/1揹包問題不同之處在於其每個物品是無限的,從每種物品的角度考慮,與它相關的策略就變成了取0件、1件、2件…。我們可以根據0/1揹包的思路,對狀態轉移方程進行改進,令f[i][v]表示前 i 種物品恰放入一個容量爲 v 的揹包的最大權值。狀態轉移方程就變成了:

f[ i ][ v ] = max{ f[ i-1 ][ v-k*w[i] ] + k*val[ i ]  | 0 <= k*w[i] <=  v}

我們通過對0/1揹包的思路加以改進,就得到了完全揹包的一種解法,這種解法時間複雜度爲O(n3n^3),空間複雜度爲O(n2n^2)。

3.3 時間優化:

根據上述f[ i ][ v ]的定義,其爲前 i 種物品恰好放入容量爲 v 的揹包的最大權值。根據上述狀態轉移方程可知,我們假設的是子結果f[ i-1 ][ v-k*w[i] ]中並沒有選入第 i 種物品,所以我們需要逆序遍歷(像0/1揹包一樣)來確保該前提;但是我們現在考慮“加選一件第 i 種物品”這種策略時,正需要一個可能已經選入第 i 種物品的子結果f[ i ][ v-w[i] ],於是當我們順序遍歷時,就剛好達到該要求。這種做法,使我們省去了一層循環,即第 i 種物品放入的件數k,從而時間複雜度優化爲O(n^2)。

3.4 空間優化:

正如0/1揹包的空間優化,上述狀態轉移方程已經優化爲:

f[i][v]=max{f[i-1][v],f[i][v-w[i]]+val[i]}

將這個方程用一維數組實現,便得到了如下僞代碼:

for i = 1..N

   for v = 0..V

     f[v] = max{f[v],f[v-w[i]] + val[ i ] };

3.5 小剪枝:

完全揹包問題有一個很簡單有效的優化,是這樣的:若兩件物品i、j滿足w[i] <= w[j]且val[i] >= val[j],則將物品j去掉,不用考慮。這個優化的正確性顯然:任何情況下都可將價值小費用高的j換成物美價廉的i,得到至少不會更差的方案。對於隨機生成的數據,這個方法往往會大大減少物品的件數,從而加快速度。然而這個並不能改善最壞情況的複雜度,因爲有可能特別設計的數據可以一件物品也去不掉。

3.6 轉化爲0/1揹包問題:

既然01揹包問題是最基本的揹包問題,那麼我們可以考慮把完全揹包問題轉化爲01揹包問題來解。最簡單的想法是,考慮到第i種物品最多選V/w[i]件,於是可以把第i種物品轉化爲V/w[i]件費用及價值均不變的物品,然後求解這個01揹包問題。這樣完全沒有改進基本思路的時間複雜度,但這畢竟給了我們將完全揹包問題轉化爲01揹包問題的思路:將一種物品拆成多件物品。

更高效的轉化方法是:把第i種物品拆成費用爲w[i]*2k、價值爲val[i]*2k的若干件物品,其中k滿足w[i]*2k<V。這是二進制的思想,因爲不管最優策略選幾件第i種物品,總可以表示成若干個2k件物品的和。這樣把每種物品拆成O(log(V/w[i])+1)件物品,是一個很大的改進。

3.7 代碼示例:完全揹包問題模板

4.多重揹包問題

4.1 問題描述:

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

4.2 解題思路:

這種類型的題目又和完全揹包有些相似,不同的就在於其數量不是無限的。基本的方程只需將完全揹包問題的方程略微一改即可,因爲對於第i種物品有n[i]+1種策略:取0件,取1件……取n[i]件。令f[i][v]表示前i種物品恰放入一個容量爲v的揹包的最大權值,則:f[i][v]=max{ f[ i-1 ][ v-kw[i] ] + kval[ i ] | 0<= k <= n[ i ]}。複雜度是O(V*∑n[i])。

4.3 轉化爲0/1揹包問題:

把第i種物品換成n[i]件01揹包中的物品,則得到了物品數爲∑n[i]的01揹包問題,直接求解,複雜度仍然是O(V*∑n[i])。

但是我們期望將它轉化爲01揹包問題之後能夠像完全揹包一樣降低複雜度。仍然考慮二進制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0…n[i]件——均能等價於取若干件代換以後的物品。另外,取超過n[i]件的策略必不能出現。

方法是:將第i種物品分成若干件物品,其中每件物品有一個係數,這件物品的費用和價值均是原來的費用和價值乘以這個係數。使這些係數分別爲 1,2,4,…,2(k-1),n[i]-2k+1,且k是滿足n[i]-2^k+1>0的最大整數(注意:這些係數已經可以組合出1~n[i]內的所有數字)。例如,如果n[i]爲13,就將這種物品分成係數分別爲1,2,4,6的四件物品。

分成的這幾件物品的係數和爲n[i],表明不可能取多於n[i]件的第i種物品。另外這種方法也能保證對於0…n[i]間的每一個整數,均可以用若干個係數的和表示,這個證明可以分0…2k-1和2k…n[i]兩段來分別討論得出,並不難,希望你自己思考嘗試一下。

這樣就將第i種物品分成了O(logn[i])種物品,將原問題轉化爲了複雜度爲O(V*∑logn[i])的01揹包問題,是很大的改進。

4.4 參考模板:例題-慶功會

5.混合三種揹包問題

5.1 問題描述:如果將01揹包、完全揹包、多重揹包混合起來。也就是說,有的物品只可以取一次(01揹包),有的物品可以取無限次(完全揹包),有的物品可以取的次數有一個上限(多重揹包)。應該怎麼求解呢?

5.2 0/1揹包與完全揹包的混合:

考慮到在01揹包和完全揹包中最後給出的僞代碼只有一處不同,故如果只有兩類物品:一類物品只能取一次,另一類物品可以取無限次,那麼只需在對每個物品應用轉移方程時,根據物品的類別選用順序或逆序的循環即可,複雜度是O(VN)。

僞代碼如下:

for i=1…N

if 第i件物品是01揹包

for v=V…0

f[v] = max{ f[ v ] , f[ v-w[i] ] + val[i] };

else if 第i件物品是完全揹包

for v=0…V

f[v] = max{ f[v] , f[v-w[i] ] + val[i] };

5.3 再加上多重揹包:

如果再加上有的物品最多可以取有限次,那麼原則上也可以給出O(VN)的解法:遇到多重揹包類型的物品用單調隊列解即可。但如果不考慮超過NOIP範圍的算法的話,用多重揹包中將每個這類物品分成O(log n[i])個01揹包的物品的方法也已經很優了。

5.4 例題:混合揹包

6.二維費用揹包問題

6.1 問題描述:

二維費用的揹包問題是指:對於每件物品,具有兩種不同的費用;選擇這件物品必須同時付出這兩種代價;對於每種代價都有一個可付出的最大值(揹包容量)。問怎樣選擇物品可以得到最大的價值。設這兩種代價分別爲代價1和代價2,第i件物品所需的兩種代價分別爲a[i]和b[i]。兩種代價可付出的最大值(兩種揹包容量)分別爲V和U。物品的價值爲c[i]。

6.2 算法:

費用加了一維,只需狀態也加一維即可。設f[i][v][u]表示前i件物品付出兩種代價分別爲v和u時可獲得的最大價值。

狀態轉移方程就是:f [i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+c[i]}。如前述方法,可以只使用二維的數組:當每件物品只可以取一次時變量v和u採用逆序的循環,當物品有如完全揹包問題時採用順序的循環。當物品有如多重揹包問題時拆分物品。

6.3 物品總個數的限制:

有時,“二維費用”的條件是以這樣一種隱含的方式給出的:最多隻能取M件物品。這事實上相當於每件物品多了一種“件數”的費用,每個物品的件數費用均爲1,可以付出的最大件數費用爲M。換句話說,設f[v][m]表示付出費用v、最多選m件時可得到的最大價值,則根據物品的類型(01、完全、多重)用不同的方法循環更新,最後在f[0…V][0…M]範圍內尋找答案。

另外,如果要求“恰取M件物品”,則在f[0…V][M]範圍內尋找答案。

6.4 例題:潛水員

7.分組揹包問題

7.1 問題描述:

有N件物品和一個容量爲V的揹包。第i件物品的費用是w[i],價值是c[i]。這些物品被劃分爲若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

7.2 算法:

這個問題變成了每組物品有若干種策略:是選擇本組的某一件,還是一件都不選。也就是說設f[k][v]表示前k組物品花費費用v能取得的最大權值,則有f[ k ][ v ]=max{ f[ k-1 ][ v ],f[ k-1 ][ v-w[ i ] ] + c[i] | 物品i屬於第k組 }。

使用一維數組的僞代碼如下:

for 所有的組k

for v=V..0

    for 所有的i屬於組k

f[v]=max{f[v],f[v-w[i]]+c[i]}

注意這裏的三層循環的順序,“for v=V…0”這一層循環必須在“for 所有的i屬於組k”之外。這樣才能保證每一組內的物品最多隻有一個會被添加到揹包中。

另外,顯然可以對每組中的物品應用完全揹包中“一個簡單有效的優化” (3.5)。

7.3 例題:分組揹包

8.有依賴的揹包問題

8.1 簡化的問題:

   這種揹包問題的物品間存在某種“依賴”的關係。也就是說,i依賴於j,表示若選物品i,則必須選物品j。爲了簡化起見,我們先設沒有某個物品既依賴於別的物品,又被別的物品所依賴;另外,沒有某件物品同時依賴多件物品。

8.2 算法:

   這個問題由NOIP2006金明的預算方案一題擴展而來。遵從該題的提法,將不依賴於別的物品的物品稱爲“主件”,依賴於某主件的物品稱爲“附件”。由這個問題的簡化條件可知所有的物品由若干主件和依賴於每個主件的一個附件集合組成。

   按照揹包問題的一般思路,僅考慮一個主件和它的附件集合。可是,可用的策略非常多,包括:一個也不選,僅選擇主件,選擇主件後再選擇一個附件,選擇主件後再選擇兩個附件……無法用狀態轉移方程來表示如此多的策略。(事實上,設有n個附件,則策略有2^n+1個,爲指數級。)

   考慮到所有這些策略都是互斥的(也就是說,你只能選擇一種策略),所以一個主件和它的附件集合實際上對應於分組的揹包中的一個物品組,每個選擇了主件又選擇了若干個附件的策略對應於這個物品組中的一個物品,其費用和價值都是這個策略中的物品的值的和。但僅僅是這一步轉化並不能給出一個好的算法,因爲物品組中的物品還是像原問題的策略一樣多。

   再考慮分組的揹包中的一句話: 可以對每組中的物品應用完全揹包中“一個簡單有效的優化”。這提示我們,對於一個物品組中的物品,所有費用相同的物品只留一個價值最大的,不影響結果。所以,我們可以對主件i的“附件集合”先進行一次01揹包,得到費用依次爲0..V-w[i]所有這些值時相應的最大價值f'[0..V-w[i]]。那麼這個主件及它的附件集合相當於V-w[i]+1個物品的物品組,其中費用爲w[i]+k的物品的價值爲f'[k]+c[i]。也就是說原來指數級的策略中有很多策略都是冗餘的,通過一次01揹包後,將主件i轉化爲 V-w[i]+1個物品的物品組,就可以直接應用分組的揹包的算法解決問題了。

   更一般的問題是:依賴關係以圖論中“森林”的形式給出(森林即多叉樹的集合),也就是說,主件的附件仍然可以具有自己的附件集合,限制只是每個物品最多隻依賴於一個物品(只有一個主件)且不出現循環依賴。

   解決這個問題仍然可以用將每個主件及其附件集合轉化爲物品組的方式。唯一不同的是,由於附件可能還有附件,就不能將每個附件都看作一個一般的01 揹包中的物品了。若這個附件也有附件集合,則它必定要被先轉化爲物品組,然後用分組的揹包問題解出主件及其附件集合所對應的附件組中各個費用的附件所對應的價值。

   事實上,這是一種樹形DP,其特點是每個父節點都需要對它的各個兒子的屬性進行一次DP以求得自己的相關屬性。這已經觸及到了“泛化物品”的思想。看完後,你會發現這個“依賴關係樹”每一個子樹都等價於一件泛化物品,求某節點爲根的子樹對應的泛化物品相當於求其所有兒子的對應的泛化物品之和。

8.3 小結:

   NOIP2006的那道揹包問題,通過引入“物品組”和“依賴”的概念可以加深對這題的理解,還可以解決它的推廣問題。用物品組的思想考慮那題中極其特殊的依賴關係:物品不能既作主件又作附件,每個主件最多有兩個附件,可以發現一個主件和它的兩個附件等價於一個由四個物品組成的物品組,這便揭示了問題的某種本質。

9.求揹包問題的方案總數

9.1 問題描述:

對於一個給定了揹包容量、物品費用、物品間相互關係(分組、依賴等)的揹包問題,除了再給定每個物品的價值後求可得到的最大價值外,還可以得到裝滿揹包或將揹包裝至某一指定容量的方案總數。

9.2 算法:

對於這類改變問法的問題,一般只需將狀態轉移方程中的max改成sum即可。例如若每件物品均是01揹包中的物品,轉移方程即爲f[i][v] = sum{ f[ i-1 ][ v ] , f[ i-1 ][ v-w[i] ] + c[i] },初始條件 f[ 0 ][ 0 ] = 1。

事實上,這樣做可行的原因在於狀態轉移方程已經考察了所有可能的揹包組成方案。

9.3 例題:貨幣系統

PS:本來想寫個人心得整理的,但無奈怎麼總結都沒原文寫的易懂,尤其是後面幾個沒怎麼接觸過的揹包問題,所以後面基本都是複製課件的,等練習練習再把updata吧。
————————————————
版權聲明:本文爲CSDN博主「AK龍」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_41162823/article/details/87878853

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章