题目:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
2.DP分析
1.状态表示:从集合(这个状态表示什么样的集合)和属性(存入的数跟集合有什么关系,一般看题目问什么)两方面。
这题里集合f[i][j]表示选到第i个东西背包容量是j的时候背包里物体的总价值
属性就是求最大值;
2.状态计算(化整为零):将前面的集合分成若干个子集;要注意不重不漏;关键是找出不同子集之间的不同点;
这题就可以分为两个子集:要选第i个物体时
(1)不把第i个物体装到背包里 (2)把第i个物体放到背包里
现在列式子
(1)选第i个物体时,不把它装进背包,就与选前一个时一样
f[i][j]=f[i-1][j]
(2)选第i个物体时,把它装进背包,那背包剩余的容量就是j-w[i]
价值就是f[ i ][ j ] = f[ i-1 ][ j-w[i] ]
选这两个子集之中更大的那个就行
二维数组的代码如下:
#include<iostream>
using namespace std;
int n,m;
int v[10005],w[10005];//value,weight
int f[500][10005];
int main()
{
cin>>n>>m;
int i,j;
for(i=1;i<=n;i++)cin>>w[i]>>v[i];
for(i=1 ; i<=n ; i++)//从第一个开始选起
for(j=0 ; j<=m ; j++)
{
f[i][j]=f[i-1][j];
if(j>=w[i]) //如果背包容量比w[i]小,肯定不把第i个装进背包,也就没有 j-w[i]
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
优化:如果用一维数组的表示这个集合,可以直接用f[j]表示容量位j时背包里物体的价值
所以循环里会变成这样(现在看看是否与前面的代码等价)
for(i=1 ; i<=n ; i++)//从第一个开始选起
for(j=0 ; j<=m ; j++)
{
f[j]=f[j];//f[i][j]=f[i-1][j]; 表示背包容量不变,只是选择了下一个物体,所以等价
if(j>=w[i])
f[j]=max(f[j],f[j-w[i]]+v[i]);//f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]); 这是先求了选上一个物体时的最大价值,改完之后是先求选目前物体时的最大价值再求选上一个物体时的最大价值,所以不等价
}
将for改一下就行
for(i=1 ; i<=n ; i++)
for(j=m ; j>=0 ; j--)
{
f[j]=f[j];
if(j>=w[i])
f[j]=max(f[j],f[j-w[i]]+v[i]);
} //让j从大到小循环,就可以与将顺序调回来
最后再简化一下就完成了,其余部分一样
for(i=1 ; i<=n ; i++)
for(j=m ; j>=w[i] ; j--)
{
f[j]=max(f[j],f[j-w[i]]+v[i]);
}