首页 > 技术文章 > 零用钱

JRX2015U43 2016-10-23 21:06 原文

【题目描述】
作為创造產奶纪录的回报,Farmer John决定开始每个星期给Bessie一点零花钱。
FJ有一些硬币,一共有N (1 <= N <= 20)种不同的面额。每一个面额都能整除所有比它大的面额。
他想用给定的硬币的集合,每个星期至少给Bessie某个零花钱的数目C (1<=C<=100000000)。请帮他计算他最多能支付多少个星期的零花钱。
【输入格式】
第一行: 两个由空格隔开的整数: N和C
第2到第N+1行: 每一行有两个整数表示一个面额的硬币:硬币面额V (1<=V<=100,000,000)和Farmer John拥有的该面额的硬币数B (1<=B<=1,000,000)
【输出格式】
一个单独的整数表示Farmer John最多能给Bessie支付多少个星期至少為C的零用钱
【样例输入】
3 6
10 1
1 100
5 120
【样例输出】
111
【分析】
从样例就可以发现,我们完全可以先把v中大于等于C的先全部支付。这显然是正确的,因为没有必要在已经满足条件时还多给Bessie一些零钱,除非是Bessie自己在写这题。
然后对于每一个星期,尽量取大的面额使得总钱数不超过C,取完后如果没有达到C就找小的面额来补。通俗的说,大的面额是主力,小的面额是救火队员,哪里缺了就补上。用程序语言就是说,当前在使用面值为v[i]的钱,已经支付了j元,而且j+v[i]或v[i-1]>j+v[1]>=C,此处默认v数组已经按照从小到大的顺序排好了。

var
  a,b:array[0..21] of longint;
  i,n,c,sum,t:longint;
  change:boolean;
procedure qsort(l,r:longint);
var
  i,j,temp,mid:longint;
begin
  i:=l; j:=r;
  mid:=a[(l+r) div 2];
  repeat
    while a[i]<mid do inc(i);
    while a[j]>mid do dec(j);
    if i<=j then
    begin
      temp:=a[i];a[i]:=a[j];a[j]:=temp;
      temp:=b[i];b[i]:=b[j];b[j]:=temp;
      inc(i);dec(j);
    end;
  until i>j;
  if l<j then qsort(l,j);
  if i<r then qsort(i,r);
end;
begin
  sum:=0;
  readln(n,c);
  for i:=1 to n do readln(a[i],b[i]);
  qsort(1,n);
  change:=true;
  while change do begin
    change:=false;
    t:=c;
    for i:=n downto 1 do//先取大的面额
      while (b[i]>0)and(t-a[i]>=0) do begin
        dec(b[i]);
        t:=t-a[i];
      end;
    for i:=1 to n do//救火队员出场
      if (b[i]>0)and(t>0) then begin
        dec(b[i]);
        t:=t-a[i];
      end;
    if t<=0 then begin//这个星期支付完毕
      change:=true;
      inc(sum);
    end;
  end;
  write(sum);
end.

推荐阅读