饮料供货

来源:互联网 发布:天猫路由m1 知乎 编辑:程序博客网 时间:2024/06/02 16:24

一、问题:

 水房中有很多饮料,上班的人每天都去拿一些饮料,管理水房的阿姨们每天都会准备很多的饮料给大家,为了提高服务质量,他们会统计大家对每种饮料的满意程度。

从阿姨的统计数据中,我们知道了大家对每一种饮料的满意度。供应饮料还有如下限制:

首先供应的饮料,总容量为V。

其次每一种饮料的单个容量都是2的方幂,什么意思呢,比如说王老吉每瓶都是2的3次幂=8L的,可口可乐都是2的四次幂=16L的,等等。

最后一个限制就是每一种饮料的容量都是有上限的。

统计数据中用饮料的名字、容量、数量、满意度描述每一种饮料。

问题是让我们根据这个数据,去求得保证最大满意度的购买量呢?

二、分析:

我们先把这个问题数学化,假设供应的饮料的种类是n,用(Si,Vi,Ci,Hi,Bi)(对应饮料名字,单瓶容量,可能的最大数量(上限),满意度,实际购买量)来表示第i种饮料(i = 0,1,2,3,4.......)。那么我们根据上面的分析,得出,饮料总量为:

那么总体满意度是(满意度是针对每瓶的,那么总体满意度就是所有瓶的满意度的和):

接下来,再看看我们题目的要求怎样用数学公式表示出来,在满足条件

的基础之上,

求得

解法一

书上再次采用了动态规划的方法进行求解,用Opt(V',i)表示从底i,i+1,i+2.........n-1 种饮料中,算出总容量为V’的方案中满意度之和的最大值。
因此,Opt(V,0)其实就是我们就要求的值,它代表的就是从0,1,2,3........n-1,总容量为V的方案之中满意度之和最大的值。
那么我们其实可以列出如下的推导公式:
这个公式的意思就是说:最优化的结果是=“选择k个第i种饮料的满意度+剩下部分不考虑第i种饮料的最优化结果”的最大值。
这种思想很好理解,接下来我们先附上代码,然后进行解释
int[V + 1][T + 1] opt;int  Cal(int V, int type){if (type == T){if (V == 0)return 0;elsereturn -INF;}if (V < 0)return -INF;else if (V == 0)return 0;else if (opt[V][type] != -1)return opt[V][type];int ret = -INF;for (int i = 0; i <= C[type]; i++){int temp = Cal(V - i*V[i], type + 1);if (temp != -INF){temp += H[type] * i;if (temp > ret)ret = temp;}}return opt[V][type]=ret;}
我们来看一下代码中是怎么实现的,首先要明白我们最终要求的结果是什么?我们要求的最终的结果是Cal(V,0),也就是说我们直接调用代码中Cal函数,传进去参数为v,o
然后我们接着看函数内部,前几行,代码中首先对type的值进行判断,看其是否等于T,然后又对V的大小进行判断,看其是否小于0,为什么要这样判断呢?原因等会再说,我们接着往下看,首先我们 令 ret = -INF是把其设置为无穷大,接着我们开始这个函数的重头戏了,也就是这个for循环,首先我们看它的i指的是什么意思呢?其实i代表的就是饮料的数量,也就是瓶数,i的限定大小就是其上限了,这个都不难理解,然后我们接着看for循环内部循环体,第一步,它先再次调用了自己函数本身,我们知道调用函数本身其实就是递归,没错,这就是递归,关键是你要明白为什么要这样递归,假设我们此时才是第一轮,那么我们此时的type是等于0的,i也是等于0的,那么在第一轮中代表我们把第0种饮料的数量设置为0的情况下,我们再去调用Cal函数,根据在写代码之前我们说的这个动态规划的思想,我们就必须把Cal函数的参数给更改一下,V就要变成总容量减去第0种饮料的容量,此时是0,type参数呢?此时我们已经把第0种饮料给固定了,接下来我们就是要从第1种饮料开始了呗,所以就把type+1.
接着我们继续,既然它又再次调用了自己,那么我们继续开Cal函数,从头看,此时我们再去看它判断的type ==T ,哎?随着它一次次的调用自己,你发现没,type总有达到T的时刻,千万注意:此时到达type=T不是说这一轮是对最后一种饮料进行分配,而是超出了最后一种,因为type 是从0开始的,这其实就是递归到了最深层,接下来该干什么了?返回呗!看此时的返回值如果V==0然后返回0,这个代表什么意思呢?就是说我们正好在分配第 T-1种饮料的时候,把V用完了,这代表什么,找到解了呗。
然后我们接着看这个对V的判断,说if (V<0)就直接返回-INF,什么意思呢?想一想,在我们不断的对每种饮料分配的过程中,我们发现V用超了,那代表什么意思?当然是没解呗!当然要返回-INF了,如果等于0呢?那么就是说当我们分配上一层的那种饮料的时候把V恰巧用完,这一层的饮料的满意度当然为0了,直接返回就行了。你再看还有一个else if,就是说V还没有用完,那就继续呗,把其值赋为-1,代表这种V这种type的饮料的情况还没有确定下来是否有解。
好了,接下来,我们再去看看for循环中的循环体,我们刚才说了调用自己的那个地方,我们接着往下看,注意:什么时候程序才会接着往下走呢?当然是递归有返回值了呗,就是下一层计算出来了我们要的temp,我们把这个temp和-INF去比较,如果是负无穷,就代表当前的情况是没解的,直接丢掉就行,如果不等于负无穷呢?代表有解,然后我们再根据当前层对type种饮料的分的数量,计算出这一层第type种的饮料贡献的满意度计算出来,然后和原来的temp去比较,其实就是和当前type种的饮料分配不同的数量的方案比较,保留最大的那个。
好了,其实我很罗嗦,但是我想说的是有些时候阅读代码还是很好的,能帮助你更加深刻的理解算法的精髓。

解法二、

书上还有一种解法,这个很巧妙。
它把不同种类的饮料的单个瓶的容量从小到大进行了排列,注意题目中给出每一种饮料的单个容量都是2的次幂。
那么,如果我们把总容量V去对2取余,如果得到的结果是1,那代表什么,就是说V是奇数,那么就是说2的次幂的饮料至少要有一瓶的,然后呢?我们就去查找单瓶容量为1L的饮料满意度为最高的。书上又说,如我们要买容量为2L的怎么办呢?我们找到2L中满意度最高的,然后再去找2个1L的满意度和最高的,然后二者进行比较,哪个高就用那个方案。

接下来就是我的思考了:
首先我先计算好,2L的能不能用1L的替代,4L的能不能用2L(注意此时结算2L的满意度的时候要考虑到其是否可以用1L的替代)的替代,8升的能不能用4L的替代。。。。。。
然后,我把V先去除以2,然后如果得出1,就代表1L的我一定要买一瓶了,然后我把V-1再去除以4,为什么呢?如果V-1除以4余数为2(只能为2或者0,因为都是2的次幂),就代表余下的升数中,我一定要买一瓶2L的,那么接下来再去看这个2L的能不能用1L的去替代呢?(我就去查找我最初计算的那个表),然后我继续,我用v -1-2,去除以8.。。。。。一直到最后余下的容量一定是2的次幂,然后就再去把这个容量根据刚才计算的数组去找到最优的值。
这个算法,我不知道是否为最优值,但是其是一个很典型的贪心算法,有人感兴趣的话可以验证一下,如果不是最优的,请告知。

0 0