Copyright (C) IOI日本委員会 1995. All rights reserved.
ある X 階建てのビルに備え付けられたエレベータを用いて、箱を下の 階から上の階へ上げることを考える。エレベータは 1 階から X 階に 向かって1回だけ上がるものとし、その際、各階に必ず止まるものとする。 エレベ−タの容量の制約から、1度に最大 N 個までしか箱を積めないもの とする。箱の運搬を始める前に、上げたい箱の一覧表が与えられる。この 一覧表に対して、最も多くの箱を上げるためには、どの箱を乗せればよいか を求めるプログラムを作成したい。
入力データは以下のようにして与える。まず、一覧表に載っている 箱の総数 M (M≦31)、ビルの階数 X (2≦X≦50)、 エレベータに一度に積める箱の最大個数 N (N≧1) をキーボード から入力し、続いて、上げたい箱の一覧表を
乗せる階 降ろす階 乗せる階 降ろす階 ... 乗せる階 降ろす階という形で入力する。このデ−タは M 行あり、各行が1つの箱を表わす。 i 行目で指定された箱を、「i 番の箱」と呼ぶことにする。 i 番の箱をエレベータに乗せる場合には、必ず 指定された階で乗せ、 かつ、指定された階で降ろすものとする。
エレベ−タに乗せる箱: x_1番 x_2番 ... x_k番
この出力は次のことを意味している:「与えられた一覧表に対しては、 エレベータを1階から最上階(X 階)まで1回だけ動かすとき、途中、 エレベ−タの容量をオ−バ−しないように箱を積むものとして、最大 k 個 の箱を上げることができる。そのためには、x_1番, x_2番, ..., x_k番の箱をエレベータに乗せて運べばよい。」 一般に、このような k 個の箱の選び方は1通りには定まらないかもしれない が、本問では、そのような選び方のうちの1つを出力すればよいものとする。
プログラムの概要は以下の通りである。まず、変数 combi の
i 番目のビットで i 番目の箱の選択を表す。すなわち、i 番目の
ビットが1のときは、i 番目の箱をエレベータに乗せないことを表す。
関数 c_first(m)(BASIC では cFirst(m))は、
「m 個の箱のうち、どれを乗せないか」という選び方の最初の候補を
生成する。関数 c_next(c_old, m, n)(BASIC では cNext(cOld, m, n))
は、m 個の箱のうちから、乗せない n 個
を選ぶ選び方をすべてリストアップするために用いられる。
関数 c_solve(BASIC では cSolve)が、実際に解を
求める関数である。まず、すべての箱をエレベーターに乗せたとして、
エレベータに一度に乗ることになる箱の最大個数を求める。もし、この最大
個数が N 以下ならば、すべての箱を乗せることが解となる。そうではなく、
最大個数が N を越えたとして、少なくとも ell 個の箱を乗せない
ようにする必要があったとしよう。このとき、ell 個の箱を乗せない
ようなすべての組合せについて、それが解になっているかどうかを調べる。
これでも解が得られなければ、ell を 1 増やして同様のことを調べる。
以下、同様の処理を繰り返す。
下記の3つの言語で書かれたプログラムは、上の考え方に従って 作成されている。関数 c_next(BASIC では cNext)が未完成 であるので、これを完成させなさい。用いたアルゴリズムがどのようなもの であるか、その要点を述べ(プログラム内にコメントとして書いてもよい)、 それに基づいたプログラムを書きなさい。なお、C および Pascal における 論理式の評価法はショ−トサ−キットとする。
#include <stdio.h> #define MAX_FLOOR 50 /* ビルの階数の上限 */ #define MAX_NO_BOX 32 /* 箱の個数の上限 */ typedef unsigned long int bitSEQ; /* 32ビットのビット列 --> 箱の選択を表す */ typedef struct { /* 箱についての情報を表す */ int from; /* 乗せる階 */ int toward; /* 降ろす階 */ } BOX_INFO; BOX_INFO box[MAX_NO_BOX]; /* box[i] : i+1番目の箱 */ bitSEQ bit[MAX_NO_BOX]; /* bit[i] = 00...010..00(右からi+1番目のみ1のビット列)*/ int diff[MAX_FLOOR+1]; /* diff[i] = (i階において積む箱の数)-(降ろす箱の数) */ void c_init(int no_box) /* ビット列定数 bit[i] を上記のように設定する */ { int i; bit[0] = 1; for (i=1; i<no_box; i++) bit[i] = bit[i-1]<<1; } bitSEQ c_first(int m) /* ビット列 00...011...1(1がmビット)を返す */ { int i; bitSEQ combi; for (combi=0, i=0; i<m; combi|=bit[i++]); return combi; } bitSEQ c_next(bitSEQ c_old, int m, int n) /* 現在の箱の選ばれ方を示すビット列 c_old から次のビット列 c_new を計算する. これらのビット列は、右からmビットの内のnビットだけが1でそれ以外は0である. とくに、次のビット列がない (上の条件を満たすビット列をすべて生成し終えた) ときは、すべてのビットが0のビット列を c_new とする. こうして計算した c_new を関数値とする. */ ------------------------------------------------------------------------- | | | | | 関数 c_Next の本体(ここを埋めよ) | | | | | ------------------------------------------------------------------------- int max_box(int floor) /* diff[ ]をもとにして,一度にエレベーターに乗ることになる箱数の最大値を求める */ { int i,max,sum; max = sum = 0; for (i=1; i<=floor; i++) if ((sum += diff[i]) > max) max = sum; return max; } void modify_diff(bitSEQ combi, int no_box, int s) /* 箱の選ばれ方 combi に従って diff[ ] を変更する. s=1 (s=-1) のときは,その組合せの箱を乗せない(乗せる)ように変更する. */ { int i; for (i=0; i<no_box; i++) if (bit[i] & combi) { diff[box[i].from] -= s; diff[box[i].toward] += s; } } bitSEQ c_solve(int no_box, int floor, int n) /* 箱の個数no_box,階数floor,エレベ−タに積める箱の個数の上限nの場合の解を求める */ { int is_sol,l,max; bitSEQ combi; combi = 0; max = max_box(floor); is_sol = (max <= n); for (l=max-n; !is_sol; l++) { combi = c_first(l); do { modify_diff(combi,no_box,1); is_sol = (max_box(floor) <= n); modify_diff(combi,no_box,-1); } while (!is_sol && (combi=c_next(combi,no_box,l))); } return combi; } void main() { int no_box; /* 箱の個数 */ int floor; /* ビルの階数 */ int N; /* エレベーターに積める箱の個数の上限 */ bitSEQ combi; /* エレベ−タに乗せる箱の選び方を表すビット列 */ int i,input_error; do { printf("箱の個数、ビルの階数、エレベーターに積める箱の個数を入力しなさい。\n"); scanf("%d %d %d", &no_box,&floor,&N); input_error = no_box<0 || no_box>MAX_NO_BOX || floor<2 || floor>MAX_FLOOR || N<1; if (input_error) printf("入力エラ−です。入力し直しなさい。\n"); } while (input_error); for (i=1; i<=floor; i++) diff[i] = 0; printf("箱をどの階からどの階へ上げるか入力しなさい。\n"); for (i=0; i<no_box; i++) { do { printf("箱%dは? ", i+1); scanf("%d %d", &box[i].from,&box[i].toward); if (input_error = box[i].from>=box[i].toward || box[i].from<1 || box[i].toward>floor) printf("入力エラ−です。入力し直しなさい。\n"); } while (input_error); diff[box[i].from]++; diff[box[i].toward]--; } c_init(no_box); combi = c_solve(no_box,floor,N); printf("エレベ−タに乗せる箱: "); for (i=0; i<no_box; i++) if (!(combi & bit[i])) printf("%2d番 ", i+1); }
DEFINT A-B DEFLNG C DEFINT D-Z CONST maxFloor = 50 ' ビルの階数の上限 CONST maxNoBox = 31 ' 箱の個数の上限 TYPE boxInfo ' 箱についての情報を表す from AS INTEGER ' 乗せる階 toward AS INTEGER ' 降ろす階 END TYPE DECLARE SUB cInit (m AS INTEGER) DECLARE FUNCTION cFirst (m AS INTEGER) DECLARE FUNCTION cNext (cOld AS LONG, m AS INTEGER, n AS INTEGER) DECLARE SUB modifyDiff (combi AS LONG, noBox AS INTEGER, s AS INTEGER) DECLARE FUNCTION cSolve (noBox AS INTEGER, floor AS INTEGER, n AS INTEGER) DECLARE FUNCTION maxBox (floor AS INTEGER) DEF FNcAnd (c1, c2) = (INT(c1 / c2) MOD 2) OPTION BASE 1 DIM SHARED cBit(maxNoBox) ' cBit(i) = 00...010..00(右からi番目のみ1のビット列) DIM SHARED box(maxNoBox) AS boxInfo ' box(i) : i番目の箱 DIM SHARED diff(maxFloor) AS INTEGER ' diff(i) = (i階において積む箱の数)-(降ろす箱の数) DO PRINT "箱の個数、ビルの階数、エレベーターに積める箱の個数を入力しなさい。" INPUT noBox, floor, n ' noBox: 箱の個数 floor: ビルの階数 N: エレベーターに積める箱の個数 range1 = (noBox >= 0) AND (noBox <= maxNoBox) range2 = (floor > 1) AND (floor <= maxFloor) AND (n >= 1) IF range1 AND range2 THEN EXIT DO PRINT "入力エラ−です。入力し直しなさい。" LOOP FOR i = 1 TO noBox: diff(i) = 0: NEXT i PRINT "箱をどの階からどの階へ上げるか入力しなさい。" FOR i = 1 TO noBox DO PRINT "箱"; i; "は? "; INPUT box(i).from, box(i).toward range1 = (box(i).from < box(i).toward) range2 = (box(i).from >= 1) AND (box(i).toward <= floor) IF range1 AND range2 THEN EXIT DO PRINT "入力エラ−です。入力し直しなさい。" LOOP diff(box(i).from) = diff(box(i).from) + 1 diff(box(i).toward) = diff(box(i).toward) - 1 NEXT i cInit (noBox) combi = cSolve(noBox, floor, n) PRINT "エレベ−タに乗せる箱: "; FOR i = 1 TO noBox IF FNcAnd(combi, cBit(i)) = 0 THEN PRINT USING "##番 "; i; NEXT i END FUNCTION cFirst (m AS INTEGER) ' ビット列 00...011...1(1がmビット)を返す combi = 0 FOR i = 1 TO m: combi = combi + cBit(i): NEXT i cFirst = combi END FUNCTION SUB cInit (m AS INTEGER) ' ビット列定数 bit(i) を上記のように設定する cBit(1) = 1 FOR i = 2 TO m: cBit(i) = 2 * cBit(i - 1): NEXT i END SUB FUNCTION cNext (cOld AS LONG, m AS INTEGER, n AS INTEGER) ' 現在の箱の選ばれ方を示すビット列 cOld から次のビット列 cNew を計算する. ' これらのビット列は、右からmビットの内のnビットだけが1でそれ以外は0である. ' とくに、次のビット列がない (上の条件を満たすビット列をすべて生成し終えた) ' ときは、すべてのビットが0のビット列を cNew とする. ' こうして計算した cNew を関数値とする. ------------------------------------------------------------------------- | | | | | 関数 cNext の本体(ここを埋めよ) | | | | | ------------------------------------------------------------------------- FUNCTION cSolve (noBox AS INTEGER, floor AS INTEGER, n AS INTEGER) ' 箱の個数noBox,階数floor,エレベ−タに積める箱の個数の上限nの場合の解を求める combi = 0: max = maxBox(floor): isSol = (max <= n) l = max - n WHILE NOT isSol combi = cFirst(l) DO CALL modifyDiff(combi, noBox, 1) isSol = (maxBox(floor) <= n) CALL modifyDiff(combi, noBox, -1) IF NOT isSol THEN combi = cNext(combi, noBox, l) LOOP UNTIL isSol OR (combi = 0) l = l + 1 WEND cSolve = combi END FUNCTION FUNCTION maxBox (floor AS INTEGER) ' diff をもとにして,一度にエレベーターに乗ることになる箱数の最大値を求める max = 0: sum = 0 FOR i = 1 TO floor sum = sum + diff(i) IF sum > max THEN max = sum NEXT i maxBox = max END FUNCTION SUB modifyDiff (combi AS LONG, noBox AS INTEGER, s AS INTEGER) ' 箱の選ばれ方 combi に従って diff を変更する. ' s=1 (s=-1) のときは,その組合せの箱を乗せない(乗せる)ように変更する. FOR i = 1 TO noBox IF FNcAnd(combi, cBit(i)) <> 0 THEN diff(box(i).from) = diff(box(i).from) - s diff(box(i).toward) = diff(box(i).toward) + s END IF NEXT i END SUB
program problem1; const max_floor = 50; {ビルの階数の上限} max_no_box = 31; {箱の個数の上限} type bit_seq = longint; {31ビットのビット列 --> 箱の選択を表す} box_info = record {箱についての情報を表す} from: integer; {乗せる階} toward: integer; {降ろす階} end; var bit: array [1..max_no_box] of bit_seq; { bit[i] = 00...010..00(右からi番目のみ1のビット列)} box: array [1..max_no_box] of box_info; {box[i] : i番目の箱} diff: array [1..max_floor] of integer; { diff[i] =(i階において積む箱の数)-(降ろす箱の数)} procedure c_init(no_box: integer); { ビット列定数 bit[i] を上記のように設定する } var i: integer; begin bit[1] := 1; for i := 2 to no_box do bit[i] := bit[i-1] shl 1; end; function c_first(m: integer): bit_seq; { ビット列 00...011...1(1がmビット)を返す } var i: integer; combi: bit_seq; begin combi := 0; for i := 1 to m do combi := (combi or bit[i]); c_first := combi; end; function c_next(c_old: bit_seq; m,n: integer): bit_seq; { 現在の箱の選ばれ方を示すビット列 c_old から次のビット列 c_new を計算する. これらのビット列は、右からmビットの内のnビットだけが1でそれ以外は0である. とくに、次のビット列がない (上の条件を満たすビット列をすべて生成し終えた) ときは、すべてのビットが0のビット列を c_new とする. こうして計算した c_new を関数値とする. } var c_new: bit_seq; ------------------------------------------------------------------------- | | | | | 関数 cNext の本体(ここを埋めよ) | | | | | ------------------------------------------------------------------------- function max_box(floor: integer): integer; { diff[ ]をもとにして,一度にエレベーターに乗ることになる箱数の最大値を求める } var i,sum,max: integer; begin sum := 0; max := 0; for i := 1 to floor do begin sum := sum + diff[i]; if sum > max then max := sum; end; max_box := max; end; procedure modify_diff(combi: bit_seq; no_box,s:integer); { 箱の選ばれ方 combi に従って diff[ ] を変更する. s=1 (s=-1) のときは,その組合せの箱を乗せない(乗せる)ように変更する. } var i: integer; begin for i := 1 to no_box do if combi and bit[i] <> 0 then with box[i] do begin diff[from] := diff[from] - s; diff[toward] := diff[toward] + s; end; end; function c_solve(no_box,floor,n: integer): bit_seq; { 箱の個数no_box,階数floor,エレベ−タに積める箱の個数の上限nの場合の解を求める } var l,max: integer; is_sol: boolean; combi: bit_seq; begin combi := 0; max := max_box(floor); is_sol := (max <= n); l := max - n; while not is_sol do begin combi := c_first(l); repeat modify_diff(combi,no_box,1); is_sol := (max_box(floor) <= n); modify_diff(combi,no_box,-1); if not is_sol then combi := c_next(combi,no_box,l); until is_sol or (combi = 0); inc(l); end; c_solve := combi; end; var no_box: integer; {荷物の個数} floor: integer; {ビルの階数} N: integer; {エレベーターに積める箱の個数} combi: bit_seq; {エレベ−タに乗せる箱の選び方を表すビット列} i: integer; input_error: boolean; begin { main } writeln('箱の個数、ビルの階数、エレベーターに積める箱の個数を入力しなさい。'); repeat readln(no_box,floor,N); input_error := (no_box < 0) or (no_box > max_no_box) or (floor < 2) or (floor > max_floor) or (N < 1); if input_error then writeln('入力エラ−です。入力し直しなさい。'); until not input_error; for i := 1 to floor do diff[i] := 0; writeln('箱をどの階からどの階へ上げるか入力しなさい。'); for i := 1 to no_box do with box[i] do begin repeat write('箱', i, 'は? '); read(from, toward); input_error := (from >= toward) or (from < 1) or (toward > floor); if input_error then writeln('入力エラ−です。入力し直しなさい。'); until not input_error; inc(diff[from]); dec(diff[toward]); end; c_init(no_box); combi := c_solve(no_box,floor,N); write('エレベ−タに乗せる箱: '); for i := 1 to no_box do if combi and bit[i] = 0 then write(i:2,'番 '); end.
JOIホームページへ戻る
JOI'95へ戻る