商店购物(IOI’95)

一、问题描述

   某商店中每种商品都有一个价格。例如,一朵花的价格是2 ICU(ICU 是信息学竞赛的货币的单位);一个花瓶的价格是5 ICU。为了吸引更多的顾客,商店提供了特殊优惠价。

    特殊优惠商品是把一种或几种商品分成一组。并降价销售。例如:3朵花的价格不是6而是5 ICU ;2个花瓶加1朵花是10 ICU不是12 ICU

    编一个程序,计算某个顾客所购商品应付的费用。要充分利用优惠价以使顾客付款最小。请注意,你不能变更顾客所购商品的种类及数量,即使增加某些商品会使付款总数减小也不允许你作出任何变更。假定各种商品价格用优惠价如上所述,并且某顾客购买物品为:3朵花和2个花瓶。那么顾客应付款为14 ICU因为:

         1朵花加2个花瓶: 优惠价:10  ICU

         2朵花            正常价: 4  ICU

输入数据

    用两个文件表示输入数据。第一个文件INPUTTXT描述顾客所购物品(放在购物筐中);第二个文件描述商店提供的优惠商品及价格(文件名为OFFERTXT)。 两个文件中都只用整数。

    第一个文件INPUTTXT的格式为:第一行是一个数字B0B5),表示所购商品种类数。下面共B行,每行中含3个数CKPC 代表商品的编码(每种商品有一个唯一的编码),1C999K代表该种商品购买总数,1K5P 是该种商品的正常单价(每件商品的价格),1P999。请注意,购物筐中最多可放5*525件商品。

第二个文件OFFERTXT的格式为:第一行是一个数字S0S99),表示共有S种优惠。下面共S行,每一行描述一种优惠商品的组合中商品的种类。下面接着是几个数字对(CK),其中C代表商品编码,1C9 99K代表该种商品在此组合中的数量,1K5。本行最后一个数字P1 P9999)代表此商品组合的优惠价。当然, 优惠价要低于该组合中商品正常价之总和。

 

输出数据

在输出文件OUTPUTTXT中写 一个数字(占一行), 该数字表示顾客所购商品(输入文件指明所购商品)应付的最低货款。

二、分析

       初看这道题目,我的感觉是似曾相识,同我们做的背包问题差不多。只是背包问题是给定容量,求最大价值的东西。而这道题目是给定所放的东西,求最小的费用(对应背包问题为最小的容量)。恰好是一个求最值的“逆问题”。背包问题是经典的动态规划问题,那么这道题呢?

由于动态规划要满足无后效性和最优化原理,所以我们来分析此题是否满足以上两点。先来状态表示的方法,商品不超过5种,而每种采购的数量又不超过5,那么用一个五元组来表示第I种商品买AI的最小费用。:

FA1A2A3A4A5                      1

考虑这个状态的由来,当然,我们不用优惠商品也可以买,显然这样不是最优。于是如果我们能够使用第I条商品组合的话,状态就便为了:

FA1-S­I1A2-SI2A3-SI3A4-SI4A5-SI5 2

这样的话,状态1的费用为状态2的费用加上SI的费用,而状态2的费用必须最低(很显然,用反证法即可),同时,我们也不管状态2是如何来的(因为每一个优惠商品组合的使用是没有限制的),所以本题既满足无后效性,又符合最优化原理,同时还有大量重叠子问题产生,动态规划解决此题是最好不过了。

通过对问题的分析,我们知道了状态的表示和转移的基本方法,我们很容易得到一个状态转移方程:

F [a, b, c, d, e] = Min {F [a-S1, b-S2, c-S3, d-S4, e-S5] + SaleCost [S]}

初始条件为:

F [a, b, c, d, e] = Cost [1]*a+Cost [2]*b+Cost [3]*c+Cost [4]*d+Cost [5]*e

即不用优惠的购买费用。

三、小结

  这道题还是相对较简单的,毕竟事过境迁,这已经是七八年前的题目了。时间复杂度也可以接受,为O65*99106,但常数项的影响因为商品总数小而比较突出。空间复杂度为O65),根本不是问题。具体实现时,由于输入的数据有一部分是没有意义的,比如商品中包含不需要的物品,我们可以在输入时剔除掉,以提高程序的效率。对于数据中,商品总数不足5种的,可以把不买的那几种看成是购买0件,以统一操作。

四、参考程序

program _shop;

const

     name1 = 'input.txt';

     name2 = 'output.txt';

     name3 = 'offer.txt';

type

    saletype = array[1 .. 5] of byte;

var

   f            : array[0 .. 5, 0 .. 5, 0 .. 5, 0 .. 5, 0 .. 5] of word;

   costs        : array[0 .. 5] of integer;

   code        : array[1 .. 999] of byte;

   sale         : array[1 .. 99] of saletype;

   paysale      : array[1 .. 99] of integer;

   check        : array[1 .. 99] of boolean;

   st,ed         : array[0 .. 5] of byte;

   s,b           : integer;

 

procedure init;

var i,cc,kk,pp,j        : word;

begin

     assign(input, name1);     reset(input);

     fillchar(f, sizeof(f), 0);

     fillchar(code, sizeof(code), 0);

     fillchar(sale, sizeof(sale), 0);

     fillchar(costs, sizeof(costs), 0);

     fillchar(st, sizeof(st), 0);

     fillchar(ed, sizeof(ed), 0);

     fillchar(check, sizeof(check), true);    {初始化}

     readln(b);

     for i := 1 to b do begin

       readln(cc, kk, pp);

       code[cc] := i;

       ed[i] := kk;                     {ed[i]表示第I种商品购买的数量}

       costs[i] := pp;

     end;

     close(input);

     assign(input, name3);    reset(input);

     readln(s);

     for i := 1 to s do begin

       read(cc);

       for j := 1 to cc do begin

         read(kk, pp);

         if code[kk] = 0 then begin check[i] := false; break; end;

{显然不行的组合就不用计算了}

         sale[i, code[kk]] := pp;

       end;

       readln(cc);

       paysale[i] := cc;

     end;

     close(input);

end;

 

procedure main;

var j,k  : integer;

    i    : array[1 .. 5] of byte;

    q,t  : word;

    can  : boolean;

begin

     for i[1] := st[1] to ed[1] do

       for i[2] := st[2] to ed[2] do

         for i[3] := st[3] to ed[3] do

           for i[4] := st[4] to ed[4] do

             for i[5] := st[5] to ed[5] do begin       {枚举每个状态}

               q := 0;

               for j := 1 to 5 do inc(q, costs[j] * i[j]);  {初始值为不用任何优惠}

               for j := 1 to s do                   {枚举每个优惠商品组合}

                 if check[j] then begin

                   can := true;

                   for k := 1 to 5 do

                     if i[k] < sale[j, k] then can := false; {是否适用当前组合}

                   if can then begin

                     t := paysale[j] +

                          f[

                            i[1] - sale[j, 1], i[2] - sale[j, 2],

                            i[3] - sale[j, 3], i[4] - sale[j, 4],

                            i[5] - sale[j, 5]

                          ];

                     if t < q then q := t;              {如果更优则更新}

                   end;

                 end;

               f[i[1], i[2], i[3], i[4], i[5]] := q;          {赋值}

             end;

end;

 

procedure print;  {输出}

begin

     assign(output, name2);

     rewrite(output);

     writeln(f[ed[1], ed[2], ed[3], ed[4], ed[5]]);

     close(output);

end;

 

begin

     init;

     main;

     print;

end.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值