题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入描述:
第一行有两个整数和,用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出描述:
包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
示例1
70 3
71 100
69 1
1 2
3
备注:
对于30%的数据,
对于全部的数据,。
解答
既然说到了著名的背包问题,那么就来详解一下这题吧!
初步分析
现在我们换一种思路。既然每一种物品都有价格和重量,我们优先挑选那些单位价格最高的是否可行呢?比如在下图中,我们有3种物品,他们的重量和价格分别是10, 20, 30 kg和60, 100, 120。
动态规划
既然前面两种办法都不可行,我们再来看看有没有别的方法。我们再来看这个问题。我们需要选择n个元素中的若干个来形成最优解,假定为k个。那么对于这k个元素a1, a2, ...ak来说,它们组成的物品组合必然满足总重量<=背包重量限制,而且它们的价值必然是最大的。因为它们是我们假定的最优选择嘛,肯定价值应该是最大的。假定ak是我们按照前面顺序放入的最后一个物品。它的重量为wk,它的价值为vk。既然我们前面选择的这k个元素构成了最优选择,如果我们把这个ak物品拿走,对应于k-1个物品来说,它们所涵盖的重量范围为0-(W-wk)。假定W为背包允许承重的量。假定最终的价值是V,剩下的物品所构成的价值为V-vk。这剩下的k-1个元素是不是构成了一个这种W-wk的最优解呢?
我们可以用反证法来推导。假定拿走ak这个物品后,剩下的这些物品没有构成W-wk重量范围的最佳价值选择。那么我们肯定有另外k-1个元素,他们在W-wk重量范围内构成的价值更大。如果这样的话,我们用这k-1个物品再加上第k个,他们构成的最终W重量范围内的价值就是最优的。这岂不是和我们前面假设的k个元素构成最佳矛盾了吗?所以我们可以肯定,在这k个元素里拿掉最后那个元素,前面剩下的元素依然构成一个最佳解。
现在我们经过前面的推理已经得到了一个基本的递推关系,就是一个最优解的子解集也是最优的。可是,我们该怎么来求得这个最优解呢?我们这样来看。假定我们定义一个函数c[i, w]表示到第i个元素为止,在限制总重量为w的情况下我们所能选择到的最优解。那么这个最优解要么包含有i这个物品,要么不包含,肯定是这两种情况中的一种。如果我们选择了第i个物品,那么实际上这个最优解是c[i - 1, w-wi] + vi。而如果我们没有选择第i个物品,这个最优解是c[i-1, w]。这样,实际上对于到底要不要取第i个物品,我们只要比较这两种情况,哪个的结果值更大不就是最优的么?
在前面讨论的关系里,还有一个情况我们需要考虑的就是,我们这个最优解是基于选择物品i时总重量还是在w范围内的,如果超出了呢?我们肯定不能选择它,这就和c[i-1, w]一样。
另外,对于初始的情况呢?很明显c[0, w]里不管w是多少,肯定为0。因为它表示我们一个物品都不选择的情况。c[i, 0]也一样,当我们总重量限制为0时,肯定价值为0。
有了这个关系,我们可以更进一步的来考虑代码实现了。我们有这么一个递归的关系,其中,后面的函数结果其实是依赖于前面的结果的。我们只要按照前面求出来最基础的最优条件,然后往后面一步步递推,就可以找到结果了。
我们再来考虑一下具体实现的细节。这一组物品分别有价值和重量,我们可以定义两个数组int[] v, int[] w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) x (w + 1)的规格。
部分背包问题
现在,我们从实现的角度再来考虑一下。我们这里的最优解是每次挑选性价比最高的物品。对于这一组物品来说,我们需要将他们按照性价比从最高到最低的顺序来取。我们可能需要将他们进行排序。然后再依次取出来放入背包中。假定我们已经有数组v,w,他们已经按照性价比排好序了。
一点改进:
在前面我们挑选按照性价比排好序的物品时,排序消耗了主要的时间。在这里,我们是否真的需要去把这些物品排序呢?在某些情况下,我们只要选择一堆物品,保证他们物品重量在指定范围内。如果我们一次挑出来一批这样的物品,而且他们满足这样的条件是不是更好呢?这一种思路是借鉴快速排序里对元素进行划分的思路。主要过程如下:
1. 求每个元素的单位价值,pi = vi /wi。然后数组按照pi进行划分,这样会被分成3个部分,L, M, N。其中L < M < N。这里L表示单位价值小于某个指定值的集合,M是等于这个值的集合,而N是大于这个值的集合。
2. 我们可以首先看N的集合,因为这里都是单位价值高的集合。我们将他们的重量累加,如果WN的重量等于我们期望的值W,则N中间的结果就是我们找到的结果。
3. 如果WN的重量大于W,我们需要在N集合里做进一步划分。
4. 如果WN的重量小于W,我们需要在N的基础上再去L的集合里划分,找里面大的一部分。
这样重复步骤1到4.
总结
#include <stdio.h> #include <string.h> int f[10000],w[10000],v[10000]; int max(int x,int y) { if(x>y) return x; else return y; } int main() { int t,m,i,j; memset(f,0,sizeof(f)); scanf("%d %d",&t,&m); for(i=1;i<=m;i++) { scanf("%d %d",&w[i],&v[i]); } for(i=1;i<=m;i++) { for(j=t;j>=w[i];j--) { if(w[i]<=t) f[j]=max(f[j-w[i]]+v[i],f[j]); } } printf("%d\n",f[t]); return 0; }