JOI’95 予選 問題7 解答・解説

問題7 正解 (2点 = (a)(b)各1点)

      (a) C       cost[amount]=cost[amount-value[stamp]]+1
          Pascal  cost[amount]:=cost[amount-value[stamp]]+1
          BASIC   cost(amount)=cost(amount-value(stamp))+1

      (b) C       amount -= value[last[amount]]
          Pascal  amount:=amount-value[last[amount]]
          BASIC   amount=amount-value(last(amount))
    


 金額 x 円の切手代金を額面金額がそれぞれ a1, ..., an 円の n 種類の切手で支払うとき、その最小枚数を f(x) で 表わそう。 問題文のプログラムでは x は amount で、n は num_sorts で、 ai は value[i] で、 f(x) は cost[amount] で表わされている。 f は次のように再帰的に定義できる:

   f(0)=0,
   f(x)= min{f(x-ai)+1|x≧ai (i=1, ..., n) }.

 これはそのままそれぞれの言語において再帰的関数に翻訳できるが、 それを実行すると同じ f(x) の計算が何度も重複して行なわれるので 膨大な時間がかかる。このような場合に有効なのが 動的計画法 である。f(x) の値を x の値が小さい方から順に計算して配列要素 cost[x] に格納しておき、より大きな x に対して f(x) を 計算する際には f(x-ai) の再計算は行なわずに既に計算してある value[x-ai] の値を使うというものである。動的計画法については 「国際情報オリンピック全問題解説と解答」の第5回大会第2日目問題 の解説及び第7回大会(1995年度)の第1日目問題2の解説を参照 されたい。後者(今年の国際情報オリンピック)の問題は、 本問ときわめてよく似た問題であった。

 動的計画法によって f(x)=cost[amount] を計算しているのが 手続き(関数)sub_problem である。この際同時に、f(x) を 構成する切手のうち額面金額が最大なものの番号を last[x] に 記録しておく。空欄(a)には、 f(x) < f(x-ai) のとき cost[x] を cost[x-ai]+1 で 置き換えることに相当する代入文が入る。
 make_solution は sub_problem で計算した結果を出力する 手続き(関数)である。f(x) を構成する切手を額面金額の大きい方から 一つずつ順にプリントするには、v1=value[last[x]], v2=value[last[x-v1]], v3= value[last[x-v1-v2]], ... をこの順に vi > 0 である限りプリントすればよい。 このためには、x=amount を x-vi=amount-value[last[amount]] で 置き換えること (i=1, 2, ...) を amount > 0 である限り続ければよい。 これが空欄(b)の答である。