【精講】經典問題——0/1背包問題


在n件物品取出若干件放在空間為W的背包里,

每件物品的體積為W1,W2至Wn,

與之相對應的價值為P1,P2至Pn,

對於每個物品只需要考慮選與不選兩種情況,

求解將哪些物品裝入背包可使價值總和最大。


背包問題,是DP中的經典題型,

而0/1背包是經典中的經典


建議閱讀:
B站大佬開講

百科:記憶化搜索

百科:動態規划

模板題:
vijos 0/1背包

進階練習:

vijos 1104:采葯

vijos 完全背包問題

擴展挑戰:

vijos 開心的金明

vijos 搭配購買

可以更好了解和練習


如何做這道題呢?

孔子曰:題目不會就深搜遞歸

代碼1 暴力出奇跡(不建議提交)

#include<bits/stdc++.h>
using namespace std;
int n,ans,m;
int w[1001],c[1001];
void dfs(int t,int q,int k)
{
	if(t>n)//選完
	{
		if(k>ans) ans=k;
		return;
	}
	dfs(t+1,q,k);
	if(q+w[t]<=m)dfs(t+1,q+w[t],k+c[t]);//可以就嘗試
	return;
}
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)cin>>w[i]>>c[i];
	dfs(1,0,0);
	cout<<ans;
	return 0;
}

【孔子】因炸服務器被封號


明顯,本題遞歸會裂開(時間超限)

本題每次遞歸同一個參數得出的值相同,而出現了反復對同一個參數的遞歸

對於這種問題,記憶化搜索是一個好的解決方案,並可以AC

f[n][m]表示選到第n個物品,背包用了m時得到的最大價值

代碼2: 記憶化搜索

#include<bits/stdc++.h>
using namespace std;
int n,ans,m;
int w[1005],c[1005];
int f[110][1005];//f[n][m]表示選到第n個物品,背包用了m時得到的最大價值

void dfs(int t,int q,int k)
{
	if(t>n)
	{
		if(k>ans) ans=k;
		return;
	}
	if(f[t][q]<=k)
	{
		f[t][q]=k;
		dfs(t+1,q,k);
		if(q+w[t]<=m)dfs(t+1,q+w[t],k+c[t]);
	}
	return ;
}

int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)cin>>w[i]>>c[i];
	dfs(1,0,0);
	cout<<ans;
	return 0;
}

本題雖然可以用記憶化搜索AC,

但明顯不是最優解。


既然已經可以記憶化搜索了,

那就滿足無后效性和階段性,可以DP求解

f[n][m]表示選到第n個物品,背包用了m時得到的最大價值

從前向后直接枚舉,可以放就看是不是當前最優解(是不是最大)

代碼3: 二維動態規划

#include<bits/stdc++.h>
using namespace std;
int  f[1001][1001],w,c,n,m;
int main()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w,&c);
		for(int k=0;k<=m;k++)//注意是從0開始!
		{
			f[i][k]=f[i-1][k];
			if(k>=w) f[i][k]=max(f[i][k],f[i-1][k-w]+c);
		}
	}
	printf("%d",f[n][m]);
	return 0;
}

假設n和m>=10000,那么這種方法就沒用了,

好在天無絕人之路——STL中有個vector,

不過更簡單的方法是用滾(渣)動(男)數組


可以發現,f的第一層只使用了i,i-1,m三層,

那如果用一個一維數組,只有f[m]呢?

壓力馬斯內(贊賞)


在修改第i次前,數組的值就是i-1,直接調用修改,

結束后,f[m]正是原來的f[n][m](當前是第n次修改)

那直接一維啊!


但是,一維時要倒序枚舉,

不然就會使用多次(不符合0/1性質,成了完全背包),

而且是for m~w 這個很好理解吧

代碼4:一維動態規划

#include<cstdio>
using namespace std;
int n,t,w,v,f[1001];
int max(int x,int y){
	if(x>y)return x;
	else return y;
}
int main(){
	scanf("%d%d",&n,&t);
	for(int i=1;i<=t;i++)
	{
		scanf("%d%d",&w,&v);
		for(int j=n;j>=w;j--)
			f[j]=max(f[j],f[j-w]+v);		
	}
	printf("%d",f[n]);
}

那么關於0/1背包的講解就結束了,

如有不對,務必提出


免責聲明!

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



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