在n件物品取出若干件放在空間為W的背包里,
每件物品的體積為W1,W2至Wn,
與之相對應的價值為P1,P2至Pn,
對於每個物品只需要考慮選與不選兩種情況,
求解將哪些物品裝入背包可使價值總和最大。
背包問題,是DP中的經典題型,
而0/1背包是經典中的經典
建議閱讀:
B站大佬開講
模板題:
vijos 0/1背包
進階練習:
擴展挑戰:
可以更好了解和練習
如何做這道題呢?
孔子曰:題目不會就深搜遞歸
代碼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背包的講解就結束了,
如有不對,務必提出