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


問題5.

 本問では、例えば

             SEND     DONALD
            +MORE    +GERALD
            -----    -------
            MONEY     ROBERT
のような足し算についての覆面算を解くことを考える。ここで、アルファ ベット A〜Z はそれぞれ異なる 0〜9 の数字を表わす。 ただし、どの数の左端も0ではない(例えば、上の例では、S,M,D,G,R は 0 ではない)。上の例はどちらも解が1通りしかなく、次がその解である。
             9567     526485
            +1085    +197485
            -----    -------
            10652     723970
  下記の3つの異なる言語で書かれたプログラムは、3個の文字列を右揃えで (すなわち、最も長い文字列に揃え、それより短いものは左端に空白を入れて) 入力し、可能な解をすべて出力する。ただし、入力文字は英大文字と空白だけ とする。また、文字コ−ドはASCIIとする。入力デ−タは正しいものとして、 エラ−チェックはしない。

 いずれか一つの言語について、空欄の部分を埋めよ。空欄(a),(b)それぞれ にはいくつかのステ−トメント(文)が入り、空欄(c)には条件式(論理式)が入る。 (c)は2か所あり、同じものが入る。[3点]


QuickBASICプログラム(5)

DECLARE SUB found ()
DECLARE SUB try1 (p AS INTEGER)
DECLARE SUB try2 (p AS INTEGER, d1 AS INTEGER)
DECLARE SUB try3 (p AS INTEGER, d1 AS INTEGER, d2 AS INTEGER)

CONST N = 10              '最大桁数
CONST false = 0, true = -1
CONST UNDECIDED = -1      'アルファベットに数字がまだ割り当てられていない
DIM SHARED word(1 TO 3) AS STRING '入力文字列
DIM SHARED digit(ASC(" ") TO ASC("Z")) AS INTEGER
pp                          'アルファベットに割り当てられた数字
DIM SHARED start(ASC(" ") TO ASC("Z")) AS INTEGER
                          'そのアルファベットで可能な最初の数字
DIM SHARED unused(0 TO 9) AS INTEGER  'その数字が使われているか
DIM SHARED carry(0 TO N) AS INTEGER   'その桁の桁上がり
DIM SHARED solution AS INTEGER        '解の個数
DIM SHARED length AS INTEGER          '入力文字列の最大長

DEFINT A-Z

   FOR c = ASC("A") TO ASC("Z")
       digit(c) = UNDECIDED: start(c) = 0
   NEXT c
   digit(ASC(" ")) = 0
   FOR d = 0 TO 9: unused(d) = true: NEXT d
   FOR i = 1 TO 3
       PRINT i; "番目の文字列を入力して下さい。?=";
       LINE INPUT word(i)
   NEXT i
   length = LEN(word(3))
   FOR i = 1 TO 3
       FOR j = 1 TO length
           IF MID$(word(i), j, 1) <> " " THEN EXIT FOR
       NEXT j
       start(ASC(MID$(word(i), j, 1))) = 1
   NEXT i
   carry(length) = 0: solution = 0: CALL try1(length)
   IF solution = 0 THEN PRINT "解がありません。"
END

SUB try1 (p AS INTEGER)
'最初に入力した文字列のp文字目に数字を割り当てる
    c1 = ASC(MID$(word(1), p, 1)): d1 = digit(c1)
    IF d1 = UNDECIDED THEN
       FOR d1 = start(c1) TO 9
           IF unused(d1) THEN
               ----------------------------------
              |                                  |
              |            (a)                   |
              |                                  |
               ----------------------------------
           END IF
       NEXT d1
       digit(c1) = UNDECIDED
    ELSE
       CALL try2(p, d1)
    END IF
END SUB

SUB try2 (p AS INTEGER, d1 AS INTEGER)
'2番目に入力した文字列のp文字目に数字を割り当てる
    c2 = ASC(MID$(word(2), p, 1)): d2 = digit(c2)
    IF d2 = UNDECIDED THEN
       FOR d2 = start(c2) TO 9
           IF unused(d2) THEN
               ----------------------------------
              |                                  |
              |            (b)                   |
              |                                  |
               ----------------------------------
           END IF
       NEXT d2
       digit(c2) = UNDECIDED
    ELSE
       CALL try3(p, d1, d2)
    END IF
END SUB

SUB try3 (p AS INTEGER, d1 AS INTEGER, d2 AS INTEGER)
'3番目に入力した文字列のp文字目の数字と合計の検査
    c3 = ASC(MID$(word(3), p, 1)): d3 = d1 + d2 + carry(p)
    IF d3 < 10 THEN carry(p - 1) = 0 ELSE d3 = d3 - 10: carry(p - 1) = 1
    IF digit(c3) = UNDECIDED THEN
       IF unused(d3) AND d3 >= start(c3) THEN
          digit(c3) = d3: unused(d3) = false
          IF p = 1 THEN
                 -----------
             IF |    (c)    | THEN CALL found
                 -----------
          ELSE
             CALL try1(p - 1)
          END IF
          digit(c3) = UNDECIDED: unused(d3) = true
       END IF
    ELSE
       IF digit(c3) = d3 THEN
          IF p = 1 THEN
                 -----------
             IF |    (c)    | THEN CALL found
                 -----------
          ELSE
             CALL try1(p - 1)
          END IF
       END IF
    END IF
END SUB

SUB found
'一つの解が見つかったので出力する
    solution = solution + 1
    PRINT : PRINT "解: "; solution
    FOR i = 1 TO 3
        SELECT CASE i
           CASE 1: PRINT " ";
           CASE 2: PRINT "+";
           CASE 3: FOR j = 1 TO length + 1: PRINT "-"; : NEXT j
                   PRINT : PRINT " ";
        END SELECT
        FOR j = 1 TO length
            c$ = MID$(word(i), j, 1)
            IF "A" <= c$ AND c$ <= "Z" THEN
               PRINT USING "#"; digit(ASC(c$));
            ELSE PRINT " ";
            END IF
        NEXT j
        PRINT
    NEXT i
END SUB


Cプログラム(5)

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define N 10         /* 最大桁数 */
#define UNDECIDED -1 /* アルファベットに数字がまだ割り当てられていない */

typedef enum { false, true } boolean;

char word[3][N];     /* 入力文字列 */
int digit['Z'+1];    /* アルファベットに割り当てられた数字 */
int start['Z'+1];    /* そのアルファベットで可能な最初の数字 */
boolean unused[10];  /* その数字が使われているか */
int carry[N+1];      /* その桁の桁上がり */
int solution;        /* 解の個数 */
int len;             /* 入力文字列の最大長 */

void try1(int);
void try2(int,int);
void try3(int,int,int);
void found(void);

void main()
{
    char c; int i,j,d;

    for (c='A'; c<='Z'; c++) { 
        digit[c] = UNDECIDED; start[c] = 0;
    }
    digit[' '] = 0;
    for (d=0; d<=9; d++) unused[d] = true;
    for (i=0; i<3; i++) {
        printf("%d 番目の文字列を入力して下さい。?=", i+1);
        gets(word[i]);
    }
    len = strlen(word[2]);
    for (i=0; i<3; i++) {
        for (j=0; word[i][j]==' '; j++)
            ;
        start[word[i][j]] = 1;
    }
    carry[len] = 0; solution = 0; try1(len);
    if (solution==0) printf("解がありません。\n");
}

void try1(int p)
/* 最初に入力した文字列のp文字目に数字を割り当てる */
{
    char c1; int d1;

    c1 = word[0][p-1]; d1 = digit[c1];
    if (d1==UNDECIDED) {
       for (d1=start[c1]; d1<=9; d1++)
           if (unused[d1]) {
               -----------------------------------
              |                                   |
              |              (a)                  |
              |                                   |
               -----------------------------------
           }
       digit[c1] = UNDECIDED;
    }
    else try2(p,d1);
}

void try2(int p, int d1)
/* 2番目に入力した文字列のp文字目に数字を割り当てる */
{
    char c2; int d2;

    c2 = word[1][p-1]; d2 = digit[c2]; 
    if (d2==UNDECIDED) {
       for (d2=start[c2]; d2<=9; d2++)
           if (unused[d2]) {
               -----------------------------------
              |                                   |
              |              (b)                  |
              |                                   |
               -----------------------------------
           }
       digit[c2] = UNDECIDED;
    }
    else try3(p,d1,d2);
}

void try3(int p, int d1, int d2)
/* 3番目に入力した文字列のp文字目の数字と合計の検査 */
{
    char c3; int d3;

    c3 = word[2][p-1]; d3 = d1+d2+carry[p];
    if (d3<10) carry[p-1] = 0;
    else { d3 -= 10; carry[p-1] = 1; }
    if (digit[c3]==UNDECIDED) {
       if (unused[d3] && d3>=start[c3]) {
          digit[c3] = d3; unused[d3] = false;
                           ---------
          if (p==1) { if (|   (c)   |) found(); }
                           ---------
          else try1(p-1);
          digit[c3] = UNDECIDED; unused[d3] = true;
       }
    }
    else {
       if (digit[c3]==d3) {
                           ---------
          if (p==1) { if (|   (c)   |) found(); }
                           ---------
          else try1(p-1);
       }
    }
}

void found()
/* 一つの解が見つかったので出力する */
{
    char c; int i,j;

    printf("\n解%d: \n\n", ++solution);
    for (i=0; i<3; i++) {
        switch (i) {
           case 0: putchar(' '); break;
           case 1: putchar('+'); break;
           case 2: for (j=0; j<=len; j++) putchar('-');
                   printf("\n "); break;
        }
        for (j=0; j<len; j++)
            putchar(isupper(c=word[i][j]) ? digit[c]+'0' : ' ');
        printf("\n");
    }
}


Pascalプログラム(5)

program problem5;
   const N = 10;
         UNDECIDED = -1; {アルファベットに数字がまだ割り当てられていない}
   var word: array[1..3] of string[N];    {入力文字列}
       digit: array[' '..'Z'] of integer; 
              {アルファベットに割り当てられた数字}
       start: array[' '..'Z'] of integer; 
              {そのアルファベットで可能な最初の数字}
       unused: array[0..9] of boolean;    {その数字が使われているか}
       carry: array[0..N] of integer;     {その桁の桁上がり}
       solution: integer;                 {解の個数}
       len: integer;                      {入力文字列の最大長}

   procedure try2(p,d1: integer); forward;
   procedure try3(p,d1,d2: integer); forward;
   procedure found; forward;

   procedure try1(p: integer);
   { 最初に入力した文字列のp文字目に数字を割り当てる }
      var c1: char; d1: integer;
   begin
      c1 := word[1][p]; d1 := digit[c1];
      if d1=UNDECIDED then begin
         for d1:=start[c1] to 9 do 
             if unused[d1] then begin
                 ----------------------------------------
                |                                        |
                |                 (a)                    |
                |                                        |
                 ----------------------------------------
             end;
         digit[c1] := UNDECIDED;
      end 
      else try2(p,d1);
   end;

   procedure try2(p,d1:integer);
   { 2番目に入力した文字列のp文字目に数字を割り当てる }
      var c2: char; d2: integer;
   begin
      c2 := word[2][p]; d2 := digit[c2];
      if d2=UNDECIDED then begin
         for d2:=start[c2] to 9 do 
             if unused[d2] then begin
                 ----------------------------------------
                |                                        |
                |                 (b)                    |
                |                                        |
                 ----------------------------------------
             end;
         digit[c2] := UNDECIDED;
      end 
      else try3(p,d1,d2);
   end;

   procedure try3(p,d1,d2:integer);
   { 3番目に入力した文字列のp文字目の数字と合計の検査 }
      var c3: char; d3: integer;
   begin
      c3 := word[3][p]; d3 := d1+d2+carry[p];
      if d3<10 then carry[p-1] := 0
      else begin d3 := d3-10; carry[p-1] := 1; end;
      if digit[c3]=UNDECIDED then begin
         if unused[d3] and (d3>=start[c3]) then begin
            digit[c3] := d3; unused[d3] := false;
                                  --------
            if p=1 then begin if |   (c)  | then found end
                                  --------
            else try1(p-1);
            digit[c3] := UNDECIDED; unused[d3] := true;
         end
      end 
      else begin
         if digit[c3]=d3 then begin
                                  ---------
            if p=1 then begin if |   (c)   | then found end
                                  ---------
            else try1(p-1);
         end
      end;
   end;

   procedure found; 
   { 一つの解が見つかったので出力する }
      var c: char; i,j: integer;
   begin
      solution := solution+1;
      writeln; writeln('解', solution, ': '); writeln;
      for i:=1 to 3 do begin
          case i of
             1: write(' ');
             2: write('+');
             else for j:=0 to len do write('-'); writeln; write(' ');
          end;
          for j:=1 to len do begin 
              c := word[i][j];
              if c in ['A'..'Z'] then write(digit[c]) else write(' ');
          end; 
          writeln;
      end;
   end;

   var c: char; i,j,d: integer;

begin
   for c:='A' to 'Z' do begin 
       digit[c] := UNDECIDED; start[c] := 0; 
   end;
   digit[' '] := 0;
   for d:=0 to 9 do unused[d] := true;
   for i:=1 to 3 do begin
       write(i, '番目の文字列を入力して下さい。?='); readln(word[i]);
   end;
   len := length(word[3]);
   for i:=1 to 3 do begin
       j := 1; while word[i][j]=' ' do j := j+1;
       start[word[i][j]] := 1;
   end;
   carry[len] := 0; solution := 0; try1(len);
   if solution=0 then writeln('解がありません。');
end.


JOI'95へ戻る

JOIホームページへ戻る