朝日新聞2006年8月4日パズル横丁解答

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

最大の手数:22
その時の値:87

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

#include "puzutl.h"

int find( int i)								// i:1〜99の数
{
	int           cnt = 0;				// 手数
	while(1) {
		cnt++;
		if( (i%5) == 0) i = i*4/5;	// 5で割り切れる場合は0.8倍
		else	      	  i += 7;			// 5で割り切れない場合は+7
		if( i == 100) break;				// 結果が100になった時点で終了
		if( cnt > 125) return -1;		// 見つからなかった
	}
	return    cnt;
}

int main( int argc, cstring argv[])
{
	int           max_cnt = 0, cnt;
	set<int>      nums;						// 複数あるかも知れないので念のため
	for( int i=1; i< 100; i++) {	// 1〜99の数を調べる
		if( (cnt=find(i)) >= max_cnt) { // 100になる数が見つかり,以前より手数が掛かる
			if( cnt > max_cnt) nums.clear(); // 以前より手数が掛かれば,記憶しておいた数をクリアする
			max_cnt = cnt;
			nums.insert(i);
		}
	}
	ps( "最大の手数:%d\n", max_cnt);
	set<int>::iterator ite;
	for( ite=nums.begin(); ite != nums.end(); ite++) {
		ps( "その時の値:%d\n", *ite);
	}
	return    0;
}

もう一つの状態遷移方法で解いた場合以下のようなソースになる。

#include "puzutl.h"

int next_value( int i)
{
  if( (i%5) == 0) i = i*4/5;  // 5で割り切れる場合は0.8倍
  else            i += 7;     // 5で割り切れない場合は+7
  return    i;
}

int main( int argc, cstring argv[])
{
  // 状態遷移図を作成し解く方法。
  // 1〜99までの数字の次の状態を求める。
  map<int,int>  next_number;
  for( int i=1; i< 100; i++) {
    int         n = next_value(i);
    next_number.insert( pair<int,int>(i,n));
  }
  // その中で100以上の数があれば,その次の数を求める。
  // それを繰り返す。
  map<int,int>::iterator ite;
  map<int,int>      over100;    // 100以上の数を一時的に記憶する領域
  for( ite=next_number.begin(); ite != next_number.end(); ite++) {
    if( (*ite).second >= 100) { // 100以上の数が出現した。
      int       i = (*ite).second, n;
      do {
        n       = next_value(i); // 次の遷移先を求めて
        over100.insert( pair<int,int>( i, n)); // 別の領域に記憶しておく
        i       = n;
      }while( n >= 100);        // 次の値が遷移図に無ければ求める
    }
  }
  // 100未満の数と100以上の数をmergeして,next_number に入れる。結果 next_number は状態遷移図を示すことになる。
  for( ite=over100.begin(); ite != over100.end(); ite++) {
    int         i = (*ite).first;
    int         n = next_value(i);
    next_number.insert( pair<int,int>( i, n));
  }
  // 状態遷移の探索をするため最大値を求めておく。
  int           max_number = 0;
  for( ite=next_number.begin(); ite != next_number.end(); ite++) {
    max_number  = max( (*ite).first, max_number);
  }
  byte*         used = new byte[max_number+1]; // 訪問済みかどうか調べるために利用する
  int           max_cnt = 0, cnt;
  set<int>      nums;           // 複数あるかも知れないので念のため
  // 1〜99の状態遷移を調べる。
  for( i=1; i< 100; i++) {
    ite         = next_number.find(i);
    int         v = (*ite).first, v_next = (*ite).second;
    memset( used, 0, max_number);
    cnt         = 0;
    while( !used[v_next]) {
      cnt++;
      used[v_next]  = 1;
      if( v_next == 100) break;
      ite       = next_number.find(v_next);
      v_next    = (*ite).second;
    }
    if( v_next == 100 &&        // 対象となるのは100になった場合だけ
        cnt >= max_cnt) {       // 最大手数の場合を記録する
      if( cnt > max_cnt) { // 今までより多く手数が掛かっている場合は過去の記録を削除する
        nums.clear();
        max_cnt   = cnt;
      }
      nums.insert(v);
    }
  }
  // 解を出力する。
  {
    ps( "最大の手数:%d\n", max_cnt);
    set<int>::iterator    ite;
    for( ite=nums.begin(); ite != nums.end(); ite++) {
      ps( "その時の値:%d\n", *ite);
    }
  }
  return    0;
}