1. 程式人生 > 其它 >[NOIP2006 提高組]金明的預算方案(01揹包)

[NOIP2006 提高組]金明的預算方案(01揹包)

以經典題目“金明的預算方案”為契機,總結了揹包問題這一類模板性較強的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]);

下面附有常見揹包問題模板:

  1. 滾動陣列優化 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]);
  1. 滾動陣列優化完全揹包(每種物品數目無限):
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]);
  1. 多重揹包(每種物品個數有限):在時間複雜度要求不嚴格的情況下,可以轉化為 01 揹包處理(把同種物品視為不同物品)。

  2. 分組揹包(物品分成 \(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]);

THE END