在 节假日的时候,书店一般都会做促销活动。由于《哈利波特》系列相当畅销,店长决定通过促销活动来回馈读者。在销售的《哈利波特》平装本系列中,一共有五 卷,用编号0, 1, 2, 3, 4来表示。假设每一卷单独销售均需要8欧元。如果读者一次购买不同的两卷,就可以扣除5%的费用,三卷则更多。假设具体折扣的情况如下:
本数 折扣
2 5%
3 10%
4 20%
5 25%
在一份订单中,根据购买的卷数以及本书,就会出现可以应用不同折扣规则的情况。但是,一本书只会应用一个折扣规则。比如,读者一共买了两本卷一,一本卷二。那么,可以享受到5%的折扣。另外一本卷一则不能享受折扣。如果有多种折扣,希望能够计算出的总额尽可能的低。
要求根据这样的需求,设计出算法,能够计算出读者所购买一批书的最低价格。
分析与解法
怎么购买比较省钱呢?第一个感觉,当然是先优先考虑最大折扣,然后再次之。
这的确是一个有效的办法。那么这个算法是不是最省钱的呢?如果我们要买两本第一卷,两本第二卷,两本第三卷,一本第四卷,一本第五卷。
按照优先考 虑最大折扣的策略,我们先买各卷各一本,花去5×8×(1-25%)=30,再买第一,二,三卷,花去3×8×(1-10%)=21.6,总共51.6欧 元。但是如果我们换一个策略,先买第一、二、三、四卷,然后再买第一、二、三、五卷,那么总共花去2×(4×8×(1-20%)=51.2欧元。
从上面我们可以发现,这些折扣有一定的规律。在同样是买8本书的情况下,选择分别按照3本和5本,以及按照两个4本的付法,费用会不一样。也就是说,针对这个问题试图用贪心策略行不通。
【解法一】
那么针对这种问题的解法,动态规划是一个不错的选择。那么,不妨试一下动态规划的方法。
首先,在使用动态规划之前,得考虑怎么表达购买中间出现的状态。假设我们用Xn来表示购买第n卷书籍的数量。如果要买X1本第一卷,X2本第二卷,X3本第三卷,X4本第四卷,X5本第五卷,那么我们可以用(X1, X2, X3, X4, X5)表示我们要买的书,而F(X1, X2, X3, X4, X5)表示我们要买这些书需要的最少花费。
如果我们要买X3本第一卷,X2本第二卷,X1本第三卷,X4本第四卷,X5本第五卷呢?是否需要用F(X3, X2, X1, X4, X5)来表示呢?其实不难看出,因为各卷的价格一样,需要的最少花费仍然等于F(X1, X2, X3, X4, X5)。也就是说,F(X1, X2, X3, X4, X5)等价于F(X3, X2, X1, X4, X5)。因此我们没有必要区分不同的卷。那么对于所有跟(X1, X2, X3, X4, X5)等价的情况,我们用什么来表示呢?F(X1, X2, X3, X4, X5)还是F(X1, X2, X3, X5, X4)?还是……
根据排列组合的规则,最多有5!种可选择的表示方法,我们可以选择一种特别的表示(Y1, Y2, Y3, Y4, Y5)(其中,Yn用来表示购买第n卷书籍的数量,Y1, Y2, Y3, Y4, Y5是X1, X2, X3, X4, X5的重新排列,满足Y1>=Y2>=Y3>=Y4>=Y5),我们称它为所有跟(X1, X2, X3, X4, X5)等价的情况的“最小表示”。
接下来,就需要考虑怎么把一个大问题转化为小一点的问题。
假定要买的书为(Y1, Y2, Y3, Y4, Y5)。如果第一次考虑为5本不同卷付钱(当然这里需要保证Y5>=1),那么剩下还要再付钱的书集合为(Y1-1, Y2-1, Y3-1, Y4-1, Y5-1)。如果第一次考虑买4本不同卷(Y4>=1),那么我们接下来还需要买的书集合为(Y1-1, Y2-1, Y3-1, Y4-1, Y5)。
根据题意,只要是不同卷的书组合起来就能享受折扣,至于具体是哪几卷,并不要求。因此,就可以不用考虑其他可能比如(Y1-1, Y2-1, Y3-1, Y4, Y5-1)。
其他同理,根据如上的推理可以得到状态转移方程:
F(Y1, Y2, Y3, Y4, Y5)
= 0 if (Y1 = Y2 = Y3 = Y4 = Y5 = 0)
= min {
5 × 8 ×(1 – 25%)+ F(Y1-1, Y2-1, Y3-1, Y4-1, Y5-1), if(Y5 >= 1)
4 × 8 ×(1 – 20%)+ F(Y1-1, Y2-1, Y3-1, Y4-1, Y5), if(Y4 >= 1)
3 × 8 ×(1 – 10%)+ F(Y1-1,Y 2-1, Y3-1, Y4, Y5), if(Y3 >= 1)
2 × 8 ×(1 – 5%) + F(Y1-1, Y2-1, Y3, Y4, Y5), if(Y2 >= 1)
8 + F(Y1-1, Y2, Y3, Y4, Y5) if(Y1 >= 1)
}
状态转化之后得到的(Y1-1, Y2-1, Y3-1, Y4-1, Y5)等可能不是“最小表示”,我们需要把它们转化为对应的最小表示。比如:
F(2, 2, 2, 2, 2)
= min {
5 × 8 ×(1 – 25%)+ F(1, 1, 1, 1, 1),
4 × 8 ×(1 – 20%)+ F(1, 1, 1, 1, 2), /* 这里不是最小表示 */
3 × 8 ×(1 – 10%)+ F(1, 1, 1, 2, 2),
2 × 8 ×(1 – 5%) + F(1, 1, 2, 2, 2),
8 + F(1, 2, 2, 2, 2)
}
= min {
5 × 8 ×(1 – 25%)+ F(1, 1, 1, 1, 1),
4 × 8 ×(1 – 20%)+ F(2, 1, 1, 1, 1), /* 转换为最小表示 */
3 × 8 ×(1 – 10%)+ F(2, 2, 1, 1, 1),
2 × 8 ×(1 – 5%)+ F(2, 2, 2, 1, 1),
8 + F(2, 2, 2, 2, 1)
}
从上面的表示公式中可以看出,整个动态规划的算法需要耗费O(Y1×Y2×Y3×Y4×Y5)的空间来保存状态的值,所需要的时间复杂度也为O(Y1×Y2×Y3×Y4×Y5)。
【解法二】
对于上面的算法,时间复杂度仍然很大。那么,对于这个问题还有没有别的办法呢? 贪心策略是否有办法成功呢?
该贪心策略会在买5+3本的时候出错。因为
5×8×(1-25%)+ 3×8×(1-10%)> 4×8×(1-20%)+ 4×8×(1-20%)。实际上就是说,在购买8本书的情况下:5×25%+3×10% < 8×20%。
回过头对比一下:
在小于5本的情况下,直接按折扣买就好了:
2 5%
3 10%
4 20%
5 25%
那么如果大于5本呢。
首先,对于大于5本的情况,我们不应该考虑按一本付钱的情况,因为没有折扣。
本数 可能的分解本数 对应的折扣
6 = 4+2 4×20%+2×5% = 1.1
= 3+3 3×10%+3×10% = 0.6
= 2+2+2 6×5% = 0.3
注 明:上面的分解并不适用于所有情况。我们仅考虑的是理论上可能的最大分法,对于不能进行分解的情况,比如购买6本卷一,没有办法享受折扣,因此其折扣将小 于上述的讨论情况。同样地,对于购买5本卷一,1本卷二的情况,最多只能享受一种折扣,其折扣也会小于上述的分解情况。对于以下的分解情况,同样适用。
本数 可能的分解本数 对应的折扣
7 = 5+2 5×25%+2×5% = 1.35
= 4+3 4×20%+3×10% = 1.1
8 = 5+3 5×25%+3×10% = 1.55
= 4+4 4×20%+4×20% = 1.6
= 3+3+2 6×10%+2×5% = 0.7
= 2+2+2+2 8×5% = 0.4
9 = 5+4 5×25% + 4×20% = 2.05
= 5+2+2 5×25%+4×5% = 1.45
= 4+3+2 4×20%+3×10%+2×5% = 1.2
= 3+3+3 9×10% = 0.9
10 = 5+5 10×25% = 2.5
= 4+4+2 8×20%+2×5% = 1.7
= 4+3+3 4×20%+6×10% = 1.4
= 2+2+2+2+2 10×5% = 0.5
由于折扣的规则仅针对2到5本的情况。对于大于10本的情况,我们可以提出如下的一个假设:
假设在分解的过程中,可以找到如下的一种分法:可以把10本以上的书籍分成小于10的多组(X11, X12, X13, X14, X15),(X21, X22, X23, X24, X25)…(Xn1, Xn2, Xn3, Xn4, Xn5),并且使得只要把每组的最优解相加,就可以得到全局的最优解。
那么,对于大于10的情况,都可以分解为小于10的情况。
假设这样的分法存在,那么就可以按照小于10的情况考虑求解。如果要买的书为(Y1, Y2, Y3, Y4, Y5)(其中Y1>=Y2>=Y3>=Y4>=Y5),贪心策略建议我们买Y5本五卷的,Y4-Y5本四卷,Y3-Y4本三卷,Y2-Y3本两卷和Y1-Y2本一卷。由于贪心策略的反例,我们把K本五卷和K本三卷重新组合成2×K本四卷(K = min{Y5, Y3-Y4})。结果就是我们买Y5-K本五卷的,Y4-Y5本四卷,Y3-Y4-K本三卷,Y2-Y3本两卷和Y1-Y2本一卷(K = min{Y5, Y3-Y4})。比如我们要买3本第一卷,2本第二卷,6本第三卷,1本第四卷和7本第五卷,像前面所说的,我们要买的书可以用(7, 6, 3, 2, 1)表示。新的经过调整的贪心策略告诉我们应该买三本四卷,三本两卷和一本一卷。
那么这种分法是正确的吗?有办法证明或者找到反例吗?读者可以直接和我们联系