编程之美 - 买书问题

在 节假日的时候,书店一般都会做促销活动。由于《哈利波特》系列相当畅销,店长决定通过促销活动来回馈读者。在销售的《哈利波特》平装本系列中,一共有五 卷,用编号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)表示我们要买的书,而FX1, X2, X3, X4, X5)表示我们要买这些书需要的最少花费。

如果我们要买X3本第一卷,X2本第二卷,X1本第三卷,X4本第四卷,X5本第五卷呢?是否需要用FX3, X2, X1, X4, X5)来表示呢?其实不难看出,因为各卷的价格一样,需要的最少花费仍然等于FX1, X2, X3, X4, X5)。也就是说,FX1, X2, X3, X4, X5)等价于FX3, X2, X1, X4, X5)。因此我们没有必要区分不同的卷。那么对于所有跟(X1, X2, X3, X4, X5)等价的情况,我们用什么来表示呢?FX1, X2, X3, X4, X5)还是FX1, X2, X3, X5, X4)?还是……

根据排列组合的规则,最多有5!种可选择的表示方法,我们可以选择一种特别的表示(Y1, Y2, Y3, Y4, Y5)(其中,Yn用来表示购买第n卷书籍的数量,Y1, Y2, Y3, Y4, Y5X1, 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)。

其他同理,根据如上的推理可以得到状态转移方程:

FY1, Y2, Y3, Y4, Y5

= 0                                                                          if (Y1 = Y2 = Y3 = Y4 = Y5 = 0)

= min {

       5 × 8 ×(1 – 25%)+ FY1-1, Y2-1, Y3-1, Y4-1, Y5-1),             if(Y5 >= 1)

       4 × 8 ×(1 – 20%)+ FY1-1, Y2-1, Y3-1, Y4-1, Y5),                if(Y4 >= 1)

       3 × 8 ×(1 – 10%)+ FY1-1,Y 2-1, Y3-1, Y4, Y5),                    if(Y3 >= 1)

       2 × 8 ×(1 – 5%) + FY1-1, Y2-1, Y3, Y4, Y5),                       if(Y2 >= 1)

       8 + FY1-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)

 }

从上面的表示公式中可以看出,整个动态规划的算法需要耗费OY1×Y2×Y3×Y4×Y5)的空间来保存状态的值,所需要的时间复杂度也为OY1×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)表示。新的经过调整的贪心策略告诉我们应该买三本四卷,三本两卷和一本一卷。

那么这种分法是正确的吗?有办法证明或者找到反例吗?读者可以直接和我们联系

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值