一、问题描述
某商店中每种商品都有一个价格。例如,一朵花的价格是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
输入数据
用两个文件表示输入数据。第一个文件INPUT.TXT描述顾客所购物品(放在购物筐中);第二个文件描述商店提供的优惠商品及价格(文件名为OFFER.TXT)。 两个文件中都只用整数。
第一个文件INPUT.TXT的格式为:第一行是一个数字B(0≤B≤5),表示所购商品种类数。下面共B行,每行中含3个数C,K,P。C 代表商品的编码(每种商品有一个唯一的编码),1≤C≤999。K代表该种商品购买总数,1≤K≤5。P 是该种商品的正常单价(每件商品的价格),1≤P≤999。请注意,购物筐中最多可放5*5=25件商品。
第二个文件OFFER.TXT的格式为:第一行是一个数字S(0≤S≤99),表示共有S种优惠。下面共S行,每一行描述一种优惠商品的组合中商品的种类。下面接着是几个数字对(C,K),其中C代表商品编码,1≤C≤9 99。K代表该种商品在此组合中的数量,1≤K≤5。本行最后一个数字P(1≤ P≤9999)代表此商品组合的优惠价。当然, 优惠价要低于该组合中商品正常价之总和。
输出数据
在输出文件OUTPUT.TXT中写 一个数字(占一行), 该数字表示顾客所购商品(输入文件指明所购商品)应付的最低货款。
二、分析
初看这道题目,我的感觉是似曾相识,同我们做的背包问题差不多。只是背包问题是给定容量,求最大价值的东西。而这道题目是给定所放的东西,求最小的费用(对应背包问题为最小的容量)。恰好是一个求最值的“逆问题”。背包问题是经典的动态规划问题,那么这道题呢?
由于动态规划要满足无后效性和最优化原理,所以我们来分析此题是否满足以上两点。先来状态表示的方法,商品不超过5种,而每种采购的数量又不超过5,那么用一个五元组来表示第I种商品买AI的最小费用。:
F(A1,A2,A3,A4,A5) (1)
考虑这个状态的由来,当然,我们不用优惠商品也可以买,显然这样不是最优。于是如果我们能够使用第I条商品组合的话,状态就便为了:
F(A1-SI1,A2-SI2,A3-SI3,A4-SI4,A5-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
即不用优惠的购买费用。
三、小结
这道题还是相对较简单的,毕竟事过境迁,这已经是七八年前的题目了。时间复杂度也可以接受,为O(65*99)≈106,但常数项的影响因为商品总数小而比较突出。空间复杂度为O(65),根本不是问题。具体实现时,由于输入的数据有一部分是没有意义的,比如商品中包含不需要的物品,我们可以在输入时剔除掉,以提高程序的效率。对于数据中,商品总数不足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.