Copyright (C) IOI日本委員会 1995. All rights reserved.


問題7.

 郵便料金が与えられ、使える切手の種類が制限されているとき、 その郵便料金をそれらの切手で支払いたい。このとき、 枚数が最小となる切手の組合せを求める問題を考える。
 力は、ファイル input.txt で与えられ、 1行目には郵便料金が、2行目には使える切手が何種類あるかが、 3行目には各切手の額面が書かれている。 また、出力の1行目には、合計金額が郵便料金に等しくなるような 切手の組合せのうち枚数が最小であるのは何枚の場合であるかを、 2行目には、その最小枚数を与える切手の組合せを一つ表示する。 ただし、額面が1円の切手は必ず使えるものとする(すなわち、input.txt の3行目の最初の数字は 1 であるとする)。
次は、input.txt と出力の例である。

	input.txt 		 出力

           41            41円を構成する組合せの最小枚数: 5枚
           3 	         その組合せ: 10円 10円 10円 10円 1円
           1 10 25

 記の3つの異なる言語で書かれたプログラムは、 この問題を解くものである。 いずれか一つの言語について、空欄の部分を埋めよ。 [2点]


QuickBASICプログラム(7)

DECLARE SUB subProblem ()
DECLARE SUB makeSolution ()
CONST maxTarget = 999          ' 構成したい郵便料金の上限               
CONST maxSorts = 5             ' 切手の種類の上限                   
DIM SHARED target              ' 最終的に構成したい金額             
DIM SHARED numSorts            ' 切手の種類                         
DIM SHARED value(maxSorts + 1) ' value(i): i番目の切手の額面       
DIM SHARED cost(maxTarget + 1) ' cost(i): 金額iを構成する現時点での切手の最小枚数
DIM SHARED last(maxTarget + 1) ' last(i): 金額iを構成する現時点での最小枚数の
                               ' 組合せに最後に加えられた切手の番号 

    OPEN "input.txt" FOR INPUT AS #1
    INPUT #1, target, numSorts
    FOR stamp = 1 TO numSorts  ' stamp は切手を表す番号
        INPUT #1, value(stamp)
    NEXT stamp
    CLOSE #1

    CALL subProblem
    CALL makeSolution
END

'  サブルーチン subProblem は、target 以下の全ての金額(部分問題) amount (>1)
'  に対して、合計が amount となる枚数最小の切手の組合せ(部分解)を求めて、
'  その最小枚数を cost(amount) に、その組合せを作る際に最後に加えられた
'  切手の番号を last(amount) に格納する
SUB subProblem
    ' cost() の初期化
    FOR amount = 0 TO target    ' amount は部分問題として構成したい金額
        cost(amount) = amount
    NEXT amount
        
    FOR stamp = 1 TO numSorts: FOR amount = value(stamp) TO target
        ' stamp は切手の番号
        IF (cost(amount - value(stamp)) + 1 <= cost(amount)) THEN
             -----------------------------------------------
            |                   (a)                         |
             -----------------------------------------------
            last(amount) = stamp
        END IF
   NEXT amount: NEXT stamp
END SUB

'  サブルーチン makeSolution は金額 target を構成する切手の組合せの
'  最小枚数を出力し、last() からその組合せを再構成して出力する
SUB makeSolution
    CLS
    amount = target             ' amount は部分問題として構成したい金額
    PRINT target; "円を構成する組合せの最小枚数: "; cost(target); "枚"
    PRINT "その組合せ: ";
    WHILE amount > 0
        PRINT value(last(amount)); "円 ";
         ------------------------------------
        |              (b)                   |
         ------------------------------------
    WEND
END SUB


Cプログラム(7)

#include <stdio.h>

#define max_target 999  /* 構成したい郵便料金の上限 */
#define max_sorts  5    /* 切手の種類の上限 */

int target,             /* 最終的に構成したい郵便料金 */
    num_sorts,          /* 切手の種類数 */
    value[max_sorts+1], /* value[i]: i番目の切手の額面 */
    cost[max_target+1], /* cost[i]: 金額iを構成する現時点での切手の最小枚数 */
    last[max_target+1]; /* last[i]: 金額iを構成する現時点での最小枚数を */
                        /* 与える組合せに最後に加えられた切手の番号 */
void sub_problem(void);
void make_solution(void); 

void main()
{
    FILE *infile;
    int stamp;          /* 切手の番号 */

    infile = fopen("input.txt","rt");  
    fscanf(infile,"%d%d", &target,&num_sorts);
    for (stamp=1; stamp<=num_sorts; stamp++) fscanf(infile,"%d", &value[stamp]);
    fclose(infile);

    sub_problem();
    make_solution();
}

/*  関数 sub_problem は、target 以下の全ての金額(部分問題) amount (>1) */
/*  に対して、合計が amount となる枚数最小の切手の組合せ(部分解)を求めて、*/
/*  その最小枚数を cost[amount] に、その組合せを作る際に最後に加えられた */
/*  切手の番号を last[amount] に格納する */
void sub_problem()
{
    int stamp,          /* 切手の番号 */
        amount;         /* 部分問題として構成したい郵便料金 */

    /* cost[ ] の初期化 */
    for (amount=0; amount<=target; amount++) cost[amount] = amount;

    for (stamp=1; stamp<=num_sorts; stamp++) 
        for (amount=value[stamp]; amount<=target; amount++) 
            if (cost[amount-value[stamp]]+1 <= cost[amount]) {
                ------------------------------------------
               |                (a)                       |
                ------------------------------------------
                last[amount] = stamp;
            }
}

/*  関数 make_solution は郵便料金 target を構成する切手の組合せの */
/*  最小枚数を出力し、last[ ] からその組合せを再構成して出力する */
void make_solution() 
{
    int amount;             /* 部分問題として構成したい郵便料金 */

    printf("%d円を構成する組合せの最小枚数: %d枚", target,cost[target]);
    printf("\nその組合せ: ");
                                   --------------------------------
    for (amount=target; amount>0; |            (b)                 | )
                                   --------------------------------
        printf("%d円 ", value[last[amount]]); 
}


Pascalプログラム(7)

program problem7;
    const max_target = 999;   { 構成したい郵便料金の上限 }
    const max_sorts = 5;      { 切手の種類の上限 }
    var target: integer;      { 最終的に構成したい金額 }
        num_sorts: integer;   { 切手の種類数 }
        stamp: integer;       { 切手の番号 }
        value: array[1..max_sorts] of integer;
                    { value[i]: i番目の切手の額面 }
        cost,last: array[0..max_target] of integer;
                    { cost[i]: 金額iを構成する現時点での切手の最小枚数 }
                    { last[i]: 金額iを構成する現時点での最小枚数を与える
                               組合せに、最後に加えられた切手の番号 }
        infile: text;

    { 手続き sub_problem は、target 以下の全ての金額(部分問題) amount (>1) 
      に対して、合計が amount となる枚数最小の切手の組合せ(部分解)を求めて、
      その最小枚数を cost[amount] に、その組合せを作る際に最後に加えられた
      切手の種類を last[amount] に格納する }
    procedure sub_problem;
        var stamp : integer;  { 切手の番号 }
            amount: integer;  { 部分問題として構成したい郵便料金 }
    begin
        { cost[ ] の初期化 }    
        for amount:=0 to target do cost[amount] := amount; 

        for stamp:=1 to num_sorts do 
            for amount:=value[stamp] to target do 
                if cost[amount-value[stamp]]+1 <= cost[amount] then begin
                    --------------------------------------------
                   |                  (a)                       |
                    --------------------------------------------
                    last[amount] := stamp
                end;
    end;

    { 手続き make_solution は、金額 target を構成する切手の組合せの最小
      枚数、および、last[ ] からその組合せを再構成して出力する手続き }                   
    procedure make_solution;
        var amount: integer;   { 部分問題として構成したい郵便料金 }
    begin
        amount := target;
        writeln(target, '円を構成する組合せの最小枚数: ', cost[amount], '枚');
        write('その組み合わせ: ');
        while amount>0 do begin
            write(value[last[amount]],'円 ');
             ----------------------------------
            |                                  |
             ----------------------------------
        end;
    end;

begin  
    assign(infile,'input.txt'); reset(infile);
    readln(infile, target,num_sorts);
    for stamp:=1 to num_sorts do read(infile, value[stamp]);

    sub_problem;
    make_solution;
end.


JOI'95へ戻る

JOIホームページへ戻る