问题描述:有n个物品,第i个物品的重量为w[i],价值为v[i]。选一些物品放入背包中,使背包内物品总重量不超过W的前提下,总价值尽量大。(所有值均小于1000)
题目链接:NYOJ题目289 苹果
解题思路:若设f[i][j]表示前i(1<=i<=n)个物品选择放入容量为j(0<=j<=n)的背包中能达到的最大总价值,则n个物品放入容量为W的背包中能达到的最大总价值f[n][w]即为所求。而每个物品只有放入和不放入背包中两种选择(这也是称之为0—1背包的原因,每个物品只能放入0个或1个),当第i个物品不放入容量为j的背包时,则容量为j的背包能达到的最大价值与前i-1个物品选择放入容量为j的背包时背包能达到的最大价值相等,即此时f[i][j]=f[i-1][j]。当第i个物品放入容量为j的背包时,则容量为j的背包能达到的最大价值等于前i-1个物品选择放入容量为j-w[i]的背包能达到的最大价值与w[i]之和,即f[i][j]=f[i-1][ j-w[i] ] + w[i]。
故: if(j>=w[i])
{ f[i][j]=max( f[i-1][j] , f[i-1][ j-w[i] ]+w[i] ) }
else
{ f[i][j]=f[i-1][j] }
源代码如下:
#include <stdio.h>
#define maxn 1001
int f[maxn][maxn];
int main()
{
int n,W,w,v,i,j;
while(scanf("%d%d",&n,&W)&&(n+W))
{
for(j=0;j<=W;j++) //对f[0][j]赋初值0
f[0][j]=0;
for(i=1;i<=n;i++)
{
scanf("%d%d",&w,&v);
for(j=0;j<=W;j++)
if(j>=w&&f[i-1][j-w]+v > f[i-1][j]) //只有当j>=w[i]时,w[i]才有放入的可能
f[i][j]=f[i-1][j-w] + v;
else
f[i][j]=f[i-1][j];
}
printf("%d\n",f[n][W]);
}
return 0;
}
空间优化:滚动数组
优化思路:因为求f[i][j]时只用到f[i-1][j]和f[i-1][ j-w[i] ],且j<=j&&j-w[i]<=j,故f[i-1][j] 用过之后将其值改变也不会影响结果,所以可以用f[i-1][j]来存放f[i][j]的值,但此时必须按j从大到小依次求f[i][j]的值并存入f[i-1][j]中,因为求f[i][j]时要用到f[i][ j-w[i] ]的值且j-w[i] < j,故求f[i][j]前f[i-1][ j- w[i]]的值不能变。这样就可以用f[j]来代替f[i][j],由于每加入一个物品f[j]都要按j从大到小的顺序求一次,故f[j]被称为滚动数组。
源代码如下:
#include <stdio.h>
#include <string.h>
#define maxn 1001
int f[maxn];
int main()
{
int n,W,w,v,i,j;
while(scanf("%d%d",&n,&W)&&(n+W))
{
memset(f,0,sizeof(f)); //对f[0][i]赋初值0
for(i=1;i<=n;i++)
{
scanf("%d%d",&w,&v);
for(j=W;j>=w;j--) //只有当j>=w[i]时,w[i]才有放入的可能
if(f[j]<f[j-w] +v )
f[j]=f[j-w] +v;
}
printf("%d\n",f[W]);
}
return 0;
}
时间优化:提升j的下限值(只有当W很大时才有一定的效果)
优化思路:因为第n物品选择是否放入时只需求f[W],倒退到第n-1个物品只需要求f[ W-w[n] ] 和 f[W],即区间W-w[n]<=j<=w 上的f[j];倒退到第i个物品只需求区间W-w[n]-w[n-1]- .... -w[i+1]<= j <=W 上的f[j];故对于第i个物品j的下限为 W-w[n]-w[n-1]- ... -w[i+1]即W-sum{ w[i+1 ..... n] },但是不要忘了只有当j>=w[i]才需要更改f[j]的值,即j原本下限为w[i];故j的下限downj=max{ W-sum{ w[i+1 ..... n] } , w[j] }。
源代码如下:
#include <stdio.h>
#include <string.h>
#define maxn 1001
int w[maxn],v[maxn],f[maxn],sumi[maxn]; //sumi[i]用于存储sum{ w[1.....i] }
int main()
{
int n,W,i,j,downj;
while(scanf("%d%d",&n,&W)&&(n+W))
{
memset(f,0,sizeof(f)); //对f[0][i]赋初值0
sumi[0]=0;
for(i=1;i<=n;i++)
{
scanf("%d%d",w+i,v+i);
sumi[i]=sumi[i-1]+w[i];
}
for(i=1;i<=n;i++)
{
downj=(W-sumi[n]+sumi[i-1]);
if(downj<w[i])
downj=w[i];
for(j=W;j>=downj;j--)
if(f[j]<f[ j-w[i] ] + v[i] )
f[j]=f[ j-w[i] ] + v[i] ;
}
printf("%d\n",f[W]);
}
return 0;
}