书中已经提到了用背包方法解,复杂度为V*N*max(C),空间复杂度为O(v)
这里想了一些其它方法以及谈谈书中提到的贪心算法
方法1: 采用单调队列优化复杂度为O(V*n),空间为2*V
单调队列优化方法参考背包九讲。是可以实现的。但n很大的时候,就不是很好了
方法2:二进制表示法优化复杂度O(V*N*log(C)),空间为V
同样参考背包九讲,对C分解成1,2,4,8,2^k,x,这样任何一个1-C的数字都能有这几个数字构造成。采用01背包可做。
方法2没有方法1好。但是提出这个方法是为了引申出下一个方法。
方法3:合并容量相同的饮料,再采用二进制表示法优化 复杂度O(V*log(V)*log(Sum(C))),空间为V
首先可知log(V)是因为最多只有这么多种容量的饮料,否则2^(log(V))以上的饮料就装不下了
再有对于同一容量的饮料按幸福值大到小排序,分解出1,2,4,...2^k,x,就是按这个顺序去分组,最多Log(sum(c))组。
当然这个地方需要排序,会有一个nlog(n)的排序复杂度。
方法4:书上提到的贪心算法
显然v可以表示成为二进制数,然后用饮料构造出1,2,4....2^k的容量,去填充这个二进制数即可。
回顾一下方法3,分组的时候最前面组的由幸福值最高的饮料构成,那么本方法也是,用能够构造出2^k容量的饮料的幸福值最高的饮料去构造,然后去填充
所以实现方法为,从2^0,2^1,2^2......开始
step 1: 对于V的第k位是1,那么对于容量为2^k的饮料(包括合并得到的)按幸福值排序,选择幸福值最大的
step 2: 剩余的饮料合并成新容量为2^(k+1)的饮料,也是贪心的,幸福值最大的一起合并,可能多余1个,扔掉即可(因为肯定用不上了)
重复step1,2直到得到v的表达
复杂度分析:
V的二进制表示,那么这里有log(V)的计算
对幸福值排序,提前按容量从小到大,再幸福值从小到大排序,你n*log(n)的复杂度,可知合并操作时(step 2)是对有序的数进行合并,合并的结果也是有序的,不用重排,那么就涉及对合并后2^(k+1)的饮料排序了,归并的思路最坏O(n),所以这一步是O(n*logV)
还有一个问题就是,合并的过程会不会产生的饮料种类数增加的情况。答案当然是会啊!
举个例子,v = 16,饮料数量:15,16,16,15第一次合并7,1,7,1,7,1,7,乍一看!数量翻倍了!吓死宝宝了,要怎么办!
但是仔细想,你合并不就相当于把容量2^k和以下的饮料合并,如果这次饮料总数为N_k,那么你最多出现2*N_k的饮料啊。
所以合并操作执行以后最多2*N_k的,之前产生的新饮料是会在这个合并过程处理掉的,不会指数上升。给出伪代码:
ans = 0
sort( drink,drink + n)
for i = 0; 1 >> i <= n; i++
找到drink的区间满足容量是2^i [a,b]
如果v & (1>>i):
消耗[a,b]幸福值最大的饮料
将[a,b]的饮料合成1>>(i+1)得到新的[a,b]
归并合并[c,d],[a,b]的饮料,[c,d]是drink中容量是1>>(i+1)的饮料区间
最后的复杂度是log(v) + n*log(n) ,实现的时候空间复杂度可能需要2*n