[NOIP2006 提高組]金明的預算方案(01揹包)
阿新 • • 發佈:2021-10-20
以經典題目“金明的預算方案”為契機,總結了揹包問題這一類模板性較強的dp題目。
01 揹包轉移時的途徑只有兩種:不選,則直接跳過,考慮下一個;選,則直接加上對應的價值。本題“主件與附件”,則增加了幾種狀態(只列舉主件):
- 不選,直接下一種;
- 只選這一主件;
- 選這一主件和其對應的附件 1;
- 選這一主件和其對應的附件 2;
- 選這一主件和其對應的兩個附件。
設 \(Wmai,Cmain,Wacc,Cacc\) 四個陣列,分別記錄主件體積、主件價值、主件對應的附件 1, 2 體積,主件對應的附件 1,2 價值。於是有如下四個轉移方程(當然滾動陣列):
\( dp_j=\max(dp_j,dp_{j-Wmain_i}+Cmain_i)\\ dp_j=\max(dp_j,dp_{j-Wmain_i-Wacc_{i,0}}+Cmain_i+Cacc_{i,0})\\ dp_j=\max(dp_j,dp_{j-Wmain_i-Wacc_{i,1}}+Cmain_i+Cacc_{i,1})\\ dp_j=\max(dp_j,dp_{j-Wmain_i-Wacc_{i,0}-Wacc_{i,1}}+Cmain_i+Cacc_{i,0}+Cacc_{i,1}) \)
注意每個方程都有對應的邊界範圍,即考慮剩餘空間是否能容納。
下面是 AC 程式碼片段:
int m,n; scanf("%d%d",&m,&n);//錢數=空間, 物品數 for(int i=1,w,p,q;i<=n;++i) { scanf("%d%d%d",&w,&p,&q);//花費=體積,重要度(和價格相乘算出收益),對應主件 if(!q) { Wmain[i]=w; Cmain[i]=w*p; } else { ++Wacc[q][0];//記錄是第幾個附件 Wacc[q][Wacc[q][0]]=w; Cacc[q][Wacc[q][0]]=w*p; } } for(int i=1;i<=n;++i) { for(int j=m;Wmain[i]/*判斷是否是主件*/&&j>=Wmain[i];--j) { dp[j]=max(dp[j],dp[j-Wmain[i]]+Cmain[i]); //只選主件 if(j>=Wmain[i]+Wacc[i][1]) dp[j]=max(dp[j],dp[j-Wmain[i]-Wacc[i][1]]+Cmain[i]+Cacc[i][1]); //選主件和附件1 if(j>=Wmain[i]+Wacc[i][2]) dp[j]=max(dp[j],dp[j-Wmain[i]-Wacc[i][2]]+Cmain[i]+Cacc[i][2]); //選主件和附件2 if(j>=Wmain[i]+Wacc[i][1]+Wacc[i][2]) dp[j]=max(dp[j],dp[j-Wmain[i]-Wacc[i][1]-Wacc[i][2]]+Cmain[i]+Cacc[i][1]+Cacc[i][2]); //選主件、附件1、附件2 } } printf("%d\n",dp[m]);
下面附有常見揹包問題模板:
- 滾動陣列優化 01 揹包(各個物品不同):
for(int i=1;i<=n;++i)
for(int j=m;j>=v[i];--j)//滾動陣列,倒序跑以減免之前的影響、利用上一層的答案
dp[j]=max(dp[j],dp[j-v[i]]+c[i]);
- 滾動陣列優化完全揹包(每種物品數目無限):
for(int i=1;i<=n;++i)
for(int j=v[i];j<=m;++j)//正序跑,因為完全揹包是同種物品之間更新
dp[j]=max(dp[j],dp[j-v[i]]+c[i]);
-
多重揹包(每種物品個數有限):在時間複雜度要求不嚴格的情況下,可以轉化為 01 揹包處理(把同種物品視為不同物品)。
-
分組揹包(物品分成 \(k\) 組,每組中的物品互相沖突,只能選一件):
for(int i=1;i<=k;++i)//列舉每一組
for(int j=v;j>=0;--j)
for(auto k:a[i])//c++11特性:for-each迴圈
dp[j]=max(dp[j],dp[j-v[k]]+c[k]);