朝日新聞2007年3月30日のパズル横丁問題

問題

デジタル表示された以下の計算式がある。このうち2本の黒い棒を別の箇所に移して計算結果が最小の正の整数になるようにするには。

上記以外の数字の表示は以下の通り。

一番左の数字を0にするのはダメ。

解答への道(ヒント)

いよいよ今回で最終回。最後を黒星で終えるのは嫌だったので,右下に解答が載っているけど見ないように努力してプログラムを作成。

棒とビット位置の割り当てを以下のようにする。

一つの数字を表現するのに7本の棒を使って合計7個の数字があるので,全部で棒は49本。49ビットで表現する。ビットの割り当ては以下の通り。

これを2箇所外して,別の2箇所に取り付けるので,虱潰しコードは以下のようになる。

  A = 716-8-9 の棒の状態;
  foreach( a in 49本の棒) {
    B = A から 1本の棒aを除去
    foreach( b in 49本の棒) {
      C = B から 1本の棒bを除去
      foreach( c in 49本の棒) {
        D = C に 1本の棒cを追加
        foreach( d in 49本の棒) {
          E = D に 1本の棒dを追加
          Eの計算が出来たら計算して,最小値を記録する
        }
      }
    }
  }

単純に考えると494(5,764,801)回のループになるけど実際は枝刈りが有効に機能してそれほどの回数でも無くなる。

これを実際のコードにすると以下のようになる。

  int64         b49 =
    makebit( i2b( 7),6)|
    makebit( i2b( 1),5)|
    makebit( i2b( 6),4)|
    makebit( i2b(10),3)|        // 10は'-'
    makebit( i2b( 8),2)|
    makebit( i2b(10),1)|        // 10は'-'
    makebit( i2b( 9),0);
  A             = b49;
  for( int a=0; a< 49; a++) {
    if( !(bit64(a) & A)) continue; // 使われていない棒を調べようとしている
    B           = A & ~bit64(a);
    for( int b=0; b< 49; b++) {
      if( !(bit64(b) & B)) continue; // 使われていない棒を調べようとしている
      C         = B & ~bit64(b);
      for( int c=0; c< 49; c++) {
        if( bit64(c) & C) continue; // 既にこの棒は使っている
        D       = C | bit64(c);
        for( int d=0; d< 49; d++) {
          if( bit64(d) & D) continue; // 既にこの棒は使っている
          E       = D | bit64(d);
          int v = eval(E);      // 計算式を評価する,負数は計算不可または最初の数が0
          if( v > 0 && v < minv) { // 最小値を求める
            minv= v;
            ma = a, mb = b, mc = c, md = d;
            mA = A, mB = B, mC = C, mD = D, mE = E;
          }
        }
      }
    }
  }

利用している各関数は以下の通り。

const ulong bit0  = 0x00000001;
const ulong bit1  = 0x00000002;
const ulong bit2  = 0x00000004;
const ulong bit3  = 0x00000008;
const ulong bit4  = 0x00000010;
const ulong bit5  = 0x00000020;
const ulong bit6  = 0x00000040;

// bit  0   1   2   3   4   5   6   7   8   9   -
// .0. .0. ... .0. .0. ... .0. .0. .0. .0. .0. ... 
// 1.2 1.2 ..2 ..2 ..2 1.2 1.. 1.. 1.2 1.2 1.2 ... 
// .3. .3. ... .3. .3. .3. .3. .3. ... .3. .3. .3. 
// 4.5 4.5 ..5 4.. ..5 ..5 ..5 4.5 ..5 4.5 ..5 ... 
// .6. .6. ... .6. .6. ... .6. .6. ... .6. .6. ... 
//                0     1     2     3     4     5     6     7     8     9   '-' 
int _i2b[] = { 0x77, 0x24, 0x5d, 0x6d, 0x2e, 0x6b, 0x7b, 0x27, 0x7f, 0x6f, 0x08}; // 数字をビット表現に変換するためのテーブル
int _b2i[0x7F+10];              // ビット表現を数字に変換するためのテーブル(_i2b[]を逆に見る,main()で初期設定)

int i2b( int n)                 // 数字をビット表現に変換する
{
  assert( n >= 0 && n <= 10);   // n==10は'-'
  return    _i2b[n];
}

int b2i( int b)                 // ビット表現を数字に変換する
{
  assert( (b&~0x7F) == 0);
  return    _b2i[b];            // _b2i[]は_i2b[]を使って初期化しておく必要がある
}

int64 makebit( int v, int shift) // 1個の数字が7ビット,それを49ビット中に7個入れることが出来る
{
  int64         v64 = v;
  assert( (v&~0x7F)==0);
  v64           <<= (shift*7);  // 1つの数字で7ビット
  return    v64;
}

int64 bit64( int p)             // bit<p> を求める
{
  int64         b = 1;
  b             <<= p;
  return    b;
}

int eval( int64 e)              // 計算式を評価する
{
  int           v[7];           // 取り出した7個の数字を入れる予定
  for( int i=0; i< 7; i++) {    // 右から順に数字を取り出す
    int val = e & 0x7F;         // 7ビット取り出し
    v[i]        = b2i(val);     // それを数値に変換する
    if( v[i] < 0) return -1;    // 変換できない数字が出現した
    e           >>= 7;          // 次に取り出す数値
  }
  // v[0]〜v[6]に取り出した数字が入っている,v[0]が一番右の数字
  int           n = 0;          // 数字の個数
  int           N[7] = {0};     // 出現数字
  int           sign = 1;       // 出現数字の符号
  if( v[6] == 0) return -1;     // 先頭の数字が0の場合は題意に沿わない
  for( i=0; i< 7; i++) {
    int         val = v[7-i-1]; // 左の数字から取り出す
    if( val == 10) {            // '-'が出現した
      n++;                      // 以降,新しい数字と見なす
      sign      = -1;           // 負数が出現したことになる
      continue;                 // 次の数字を見に行く
    }
    N[n]        *= 10;          // 
    N[n]        += val * sign;  // 負数の場合も考慮して数字を評価する
  }
  int           result = 0;
  for( i=0; i<= n; i++) {       // 計算する
    result      += N[i];        // 負数はマイナスでN[i]に入っているので足すだけで済む。
  }
  return    result;             // 計算結果
}

答えが無事出力されたので右下の解答と比較,合っていることを確認。最後を白星で飾れたのでひとまず満足。

 

出題者の方2年間お疲れ様でした。

私のように間違いを繰り返すわけにはいかないので大変だったろうと思う。

私は「趣味はプログラミングです」なので,これから寂しくなるなー。

ここは会社のホームページだけとトップページがいきなり「パズル横丁」だったからこれからどうしようかな。

解速度