朝日新聞2006年12月1日パズル横丁解答

プログラムの実行結果は以下の通り。

39回進み,5歩目,25歩目で逆向きに進む。詳細は以下の通り。
  1 sum:1
  2 sum:3
  3 sum:6
  4 sum:10
* 5 sum:5
  6 sum:11
  7 sum:18
  8 sum:26
  9 sum:35
  10 sum:45
  11 sum:56
  12 sum:68
  13 sum:81
  14 sum:95
  15 sum:110
  16 sum:126
  17 sum:143
  18 sum:161
  19 sum:180 180 OK
  20 sum:200
  21 sum:221
  22 sum:243
  23 sum:266
  24 sum:290
* 25 sum:265
  26 sum:291
  27 sum:318
  28 sum:346
  29 sum:375
  30 sum:405
  31 sum:436
  32 sum:468
  33 sum:501
  34 sum:535
  35 sum:570
  36 sum:606
  37 sum:643
  38 sum:681
  39 sum:720 360 OK

プログラムのソースは以下の通り。

#include "puzutl.h"

const int       N = 100;        // まあこれくらいの数字の中に見つかるでしょ。
int             sum[1+N] = {0}; // 事前に歩数の合計値は計算しておく

YesNo check180( int n, int i, int j) // ちょうど180度で止まるか?
// n            :I:1歩,2歩,…,n歩進む
// i            :I:この歩数の時逆方向に進む
// j            :I:この歩数の時逆方向に進む
{
  int           sum = 0;        // 進んだ歩数
  for( int k=1; k<= n; k++) {   // 1歩からn歩進む
    if( k == i || k == j) sum -= k; // i,jで逆向きに進む
    else sum += k;              // i,j以外は時計回りに進む
    if( (sum+180) % 360 == 0) { // sum%180にしてしまうと360度の場合も含まれてしまうので
      return YES;               // ちょうど180度で停止した
    }
  }
  return    NO;                 // 180度で停止することはなかった。
}

int main( int argc, cstring argv[])
{
  int           minn = 0;       // 最低360度回転しなければならないので必ず進む歩数があるはず
  for( int i=1; i<= N; i++) {   // 最初にトータルの歩数を求めておく
    sum[i]      += i+sum[i-1];  // トータルの歩数は前の歩数に次の歩数を足したもの
    if( minn == 0 && sum[i] > 360) { // 最低1周する所から調べ始める
      minn      = i;            // minnを使わないで1から調べた方がバグが少ないプログラムになる
    }
  }
  for( int n=minn; n< N; n++) { // 合計数字が針の動く回数だから小さい数字から調べていく
    for( i=1; i< n; i++) {      // その中から2つの数字を選択して調べる(一つめの数字)
      for( int j=i+1; j< n; j++) { // もう一つの数字(一つめより必ず大きな数字)
        int     back = i+j;     // 2つの数字の合計
        if( ((sum[n]-back-back)%360) == 0) { // 2回引き算するのは,回ったと仮定してある分を取り消し,更にその分元に戻る
          if( check180( n, i, j)) { // その中にちょうど180で止まっているものがあるか?
            ps( "%d回進み,%d歩目,%d歩目で逆向きに進む。詳細は以下の通り。\n", n, i, j);
            int sum = 0;        // 以下詳細の出力
            for( int p=1; p<= n; p++) {
              if( p == i || p == j) sum -= p;
              else sum += p;
              ps( "%s %d sum:%d", (p==i||p==j)?"*":" ", p, sum);
              if( (sum+180)%360 == 0) ps( " 180 OK"); // sum%180にしてしまうと360度の場合も含まれてしまうので
              if( sum%360 == 0) ps( " 360 OK");
              ps( "\n");
            }
            return 0;           // 答えが見つかったので終了
          }
        }
      }
    }
  }
  pe( "答えが見つからない\n");
  return    0;
}