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


問題1.

 ある 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 における 論理式の評価法はショ−トサ−キットとする。


Cプログラム(1)

#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);
}


BASICプログラム(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


Pascalプログラム(1)

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へ戻る