(C) Copyright by IOI日本委員会 1994 All rights reserved.
以下に示すプログラムは解答の一例である。アルゴリズムは簡単である。
k 個の箱に、左から順に碁石を配ってみるだけである。
箱 1 から箱 i-1 にはすでに碁石が何個かずつ(左の箱ほど個数が多いように)
配られているとする。まだ碁石が m 個残っていて(すなわち、m > 0)、
しかも、まだ碁石が配られていない箱がある場合(すなわち、i≦k のとき)、
箱 i に m 個、m-1 個、...、1 個と多い方から順にそれぞれ配ってみる。
箱 i に j 個(1≦j≦m)の碁石を配った場合、残った m-j 個の碁石は
残りの箱 i+1 〜 箱 k に同じやり方で配る。
すべての碁石をすべての箱に
配り終わったとき(m=0 かつ i < k のとき)1つの解が得られたことになる。
以上のことを再帰的に行う関数(手続き、プロシージャ)partition(m,i,k) を
考えればよい。partition は、この例のように再帰的ではなく、
反復的に書いてもよい。
別解として、
はじめに k 個の箱に1個の碁石を配ってから残りの n-k 個を配る方法、
最初の箱に n-k+1 個を残りの k-1 個の箱に1個の碁石を配ってから
碁石を1個ずつ移動する方法、
すべての組合わせを生成して合計が n 個になるものを出力する方法
(これはあまりよくない方法であるが)、
など他にも方法はいろいろある。
C, Pascal では、解を記録するための配列 solution の代わりに 線形リスト等を用い、メモリを動的に割り当てる方がより一般性があるが、 問題文の中で解の個数の上限を指定しているので、例解ではそのような 方法は用いていない。また、 問題文の指定とは異なるが、配り方を先に出力してしまってから解の個数を 出力するのであれば、このような解の記録をとっておく必要はない。
#include < stdio.h > #define MAXk 100 /* kの上限 */ #define MAXSOL 300 /* 解(配り方)の個数の上限 */ int box[MAXk+1], /* box[i]=箱iに配られた碁石の個数 (i=1,2,...,k) */ /* ただし、box[0]=n */ count, /* 求める配り方の数 */ solution[MAXSOL][MAXk]; /* 解の記録 */ void partition(int, int, int); void print_answer(int); \end{verbatim} \newpage \begin{verbatim} void main() { int n,k; printf("nの値を入力して下さい。n="); scanf("%d", &n); printf("kの値を入力して下さい。k="); scanf("%d", &k); printf("\nn=%d, k=%d\n", n,k); if (n < =0 || k < =0 || k > =MAXk) { printf("データエラーです。\n"); exit(1); } if (n < k) { printf("可能な配り方はありません。\n"); exit(0); } count=0; box[0]=n; partition(n,1,k); print_answer(k); exit(0); } void partition(int m, int i, int k) /* 既にbox[1]〜box[i-1]には碁石が配られている。このとき、残りのm個の碁石を box[i]〜box[k]に配る配り方をすべて求める (box[i-1]≧m≧0) */ { int j; if (m==0 && i > k) { /* 得られた1つの配り方を配列solutionに記録する */ for (j=0; j < k; j++) solution[count][j]=box[j+1]; if (count++ > =MAXSOL) { printf("解の個数が多すぎます。"); exit(1); } } if (m > 0 && i < =k) { for (j=box[i-1]; j > =1; j--) { box[i]=j; partition(m-j,i+1,k); } } } void print_answer(int k) /* 答を出力する */ { int i,j; printf("可能な配り方は次の%d通りあります。\n", count); for (i=0; i < count; i++) { printf("%d", solution[i][0]); for (j=1; j < k; j++) printf(" %d", solution[i][j]); if (i < count-1) printf(", "); else printf("\n"); } }
DEFINT A-z DECLARE SUB partition(n,i,k) DECLARE SUB printAnswer(k) CONST maxk=100 ' kの上限 CONST maxsol=300 ' 解(配り方)の個数の上限 DIM SHARED box(0 TO maxk) ' box[i]=箱iに配られた碁石の個数 (i=1,2,...,k) ' ただし、box[0]=n DIM SHARED solution(maxsol, maxk) ' 解の記録 COMMON SHARED count ' 求める配り方の数 PRINT "nの値を入力して下さい。n=";: INPUT n: PRINT PRINT "kの値を入力して下さい。k=";: INPUT k: PRINT PRINT "n="; n; " k="; k IF n < =0 OR k < =0 OR k > =maxk THEN PRINT "データエラーです。": STOP IF n < k THEN PRINT "可能な配り方はありません。": STOP count=0: box(0)=n: CALL partition(n,1,k) CALL printAnswer(k) END SUB partition(m,i,k) '既にbox[1]〜box[i-1]には碁石が配られている。このとき、残りのm個の碁石を 'box[i]〜box[k]に配る配り方をすべて求める (box[i-1]≧m≧0) */ IF m=0 AND i > k THEN ' 得られた1つの配り方を配列solutionに記録する count=count+1 FOR j=1 TO k: solution(count,j)=box(j): NEXT j ELSEIF m > 0 AND i < =k THEN FOR j=box[i-1] TO 1 STEP -1 box[i]=j: CALL partition(m-j,i+1,k) NEXT j END IF END SUB SUB printAnswer(k) '答を出力する PRINT "可能な配り方は次の"; count; "通りあります。" FOR i=1 TO count PRINT solution(i,1);: FOR j=2 to k: PRINT " "; solution(i,j);: NEXT j IF i < count THEN PRINT ", "; ELSE PRINT NEXT i END SUB
program distribution; const maxk=100; { kの上限 } maxsol=300; { 解(配り方)の個数の上限 } var box : array[0..maxk] of integer; { box[i]=箱iに配られた碁石の個数 (i=1,2,...,k)、ただし、box[0]=n } n,k,count : integer; { countは求める配り方の数 } solution : array[1..maxsol, 1..maxk] of integer; { 解の記録 } procedure partition(m,i,k : integer); { 既にbox[1]〜box[i-1]には碁石が配られている。このとき、残りのm個の碁石を } { box[i]〜box[k]に配る配り方をすべて求める (box[i-1]≧m≧0) } var j : integer; begin if (m=0) and (i > k) then begin { 得られた1つの配り方を配列solutionに記録する } count:=count+1; for j:=1 to k do solution[count,j]:=box[j]; end; if (m > 0) and (i < =k) then for j:=box[i-1] downto 1 do begin box[i]:=j; partition(m-j,i+1,k); end; end; { of partition } procedure print_answer(k : integer); { 答を出力する } var i,j : integer; begin writeln('可能な配り方は次の', count, '通りあります。'); for i:=1 to count do begin write(solution[i,1]); for j:=2 to k do write(' ', solution[i,j]); if i < count then write(', ') else writeln; end; end; { of print_answer } begin write('nの値を入力して下さい。n='); readln(n); writeln; write('kの値を入力して下さい。k='); readln(k); writeln; writeln('n=', n, ', k=', k); if (n < =0) or (k < =0) or (k > =maxk) then writeln('データエラーです。') else if n < k then writeln('可能な配り方はありません。') else begin count:=0; box[0]:=n; partition(n,1,k); print_answer(k); end; end.