机试算法讲解: 第54题 0-1背包之如何采药

/*
背包:种类:0-1背包,完全背包,多重背包。
     0-1背包:含义:有一个容量为V的背包和物品,物品与两个属性,体积W和价值V,每种物品只有1个。
                  要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。
             特点:每个物品至多能选1件,物品数量只有0和1两种情况。
			 dp[i][j]:表示总体积不超过j的情况下,前i个物品所能达到的最大价值
			 初始化:dp[0][j]=0(0<=j<=s)
			 关键:采用逆序循环,保证更新dp[j]时,dp[j-list[i].w]是没有放入物品i时的数据dp[i-1][j-list[i].w]状态未因为本次更新发生改变
			 for(i = 1 ; i <= n ; i++)
			  {
				for( j = s ;j >= list[i].w ; j++)
				{
					dp[j] = max{dp[j],dp[j-list[i].w]+list[i].v}
				}
			  }
	0-1背包变体:dp[i][j]:前i个物品总体积恰好为v时所能达到的最大价值,只需改动初始化:dp[0][0]=0,其他dp[0][j]均为负无穷或不存在
             


     完全背包:一个容积为V的背包,同时有n个物品,每个物品均有各自的体积W和价值v,每个物品的数量均为无限个,求试用该背包最多能装的物品价值总和。
	          改动代码:每个物品可以被无限次选择,那么状态dp[i][j]恰好可以由可能已经放入物品i的状态dp[i][j-list[i].w]转移而来,所以这里将状态的遍历顺序变为
			  顺序,使在更新状态dp[j]时,dp[j-list[i].w]可能因为放入物品i而发生改变。
			  for(i = 1 ; i <= n ; i++)
			  {
				for(j = list[i].w ; j <= s ; j++)
				{
					dp[j] = max{dp[j],dp[j-list[i].w]+list[i].v}
				}
			  }




问题:采药。不同草药,采每一株需要一些时间,每一株有自己价值,如何让采到的价值最大。
输入:第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药数目。
     接下来的M行,每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值
输出:在规定时间内,可以猜到的草药的最大总价值
输入:
70(T采药总时间) 3(M,M行,每行是采药时间和价值)
71 100
69 1
1 2
输出:
3


思路:0-1背包:每个物品装或者不装(背包中有0个或者1个该物品)。这里总共时间=容积,物品体积=采摘每个草药时间。
     用dp[i][j]:表示总体积不超过j的情况下,前i个物品所能达到的最大价值。初始时:dp[0][j](0<=j<=V)=0。每个状态有2个状态转移来源。
	 若物品i放入背包,体积为w,价值为v,则dp[i][j] = dp[i-1][j-w]+v。即在总体积不超过j-w时,前i-1件物品可组成的最大价值的基础上再
	 加上i物品的价值v。若物品不加入背包,则dp[i][j]=dp[i-1][j]。


状态转移方程:
dp[i][j] = max{dp[i-1][j-w]+v,dp[i-1][j]};
注意:j-w的值是否为负值,若为负,则转移来源能被转移。


目标状态:dp[n][s]


关键:
1 0-1背包状态转移方程:dp[i][j] = max{dp[i-1][j-w]+v,dp[i-1][j]};,用 j >= list[i].w作区分,优化后的转移方程:dp[j] = max{dp[j-w]+v,dp[j]}
2 目标状态是:dp[n][s],s是总容量,n是物品总数
3 必须声明一个结构list:v,w表示价值和体积
4 倒序遍历j,保证确定dp[j]的值时,dp[j-list[i].w]的值尚未被修改
5 0-1背包复杂度=状态数量*状态转移复杂度=(物品数量n*背包总容积s)*O(1)=O(n*s),优化过后空间复杂度为O(s)


*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//定义背包
typedef struct List
{
	int w;//体积
	int v;//价值
}List;


int max(int a,int b)
{
	return a > b ? a:b;
}


int main(int argc,char* argv[])
{
	int s;//总容积
	int n;//n行
	int i,j;
	while(EOF!=scanf("%d %d",&s,&n))
	{
		List list[101];
		//int dp[101][1001];
		int dp[1001];//优化
		for(i = 1 ; i <= n ; i++)
		{
			scanf("%d %d",&list[i].w,&list[i].v);
		}
		//初始化状态量
		for(i = 0 ; i <= s ; i++)
		{
			//dp[0][i] = 0;
			dp[i] = 0;
		}


		//对于时间足够的情况,状态来源是:dp[i][j]为两者之中的最大值
		for(i = 1 ; i <= n ; i++)
		{
			for(j = s; j >= list[i].w ; j--)
			{
				//dp[i][j] = max(dp[i-1][j],dp[i-1][j-list[i].w] + list[i].v);
				//优化:必须倒序更新每个dp[j]的值,j小于list[i].w的各dp[j]不做更新,保持原值,即等价与dp[i][j] = dp[i-1][j]
				dp[j] = max(dp[j],dp[j-list[i].w] + list[i].v);
			}
			/*
			for(j = list[i].w-1; j >= 0 ; j--)
			{
				dp[i][j] = dp[i-1][j];
			}
			*/
		}


		//printf("%d\n",dp[n][s]);
		printf("%d\n",dp[s]);
	}
	system("pause");
	getchar();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值