背包问题之0-1背包(一)

问题描述:有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;
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值