Div2 500, Div1 250 PotatoGame

問題

スタートの数Nに対して,2人のプレイヤーが交互に数を減らしていく.減らす数は4k(1, 4, 16, 64…)で,計算結果が負の数になるのは駄目で,丁度0にしたプレイヤーが勝ちである.必勝は先攻(Taro),後攻(Hanako)のどちらか?

考え方

競技中ならDPである程度の数までの必勝側を見て条件を見つければいい.証明の点で不安残るんだろうけど.
DP プログラムの例はこちら.先攻負けで初期化しておいて,「先攻負け状態へ行けるものは先攻勝ち」になるを繰り返せば OK.

#include <iostream>
#include <vector>
using namespace std;

int main(void)
{
  const int n = 100;
  vector<bool> win(n, false);
  for( int i = 0 ; i < n ; ++i ) {
    if( win[i] ) continue;
    for( int j = 1 ; i + j < n ; j *= 4 ) win[i+j] = true;
  }
  for( int i = 0 ; i < n ; ++i ) cout << (i%10);
  cout << "\n";
  for( int i = 0 ; i < n ; ++i ) cout << "XO"[(int)win[i]];
  cout << "\n";

  return 0;
}

で,出力がこちら.

0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
XOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOOXOXOO

1 行目が N の一の位を示している.状況的に N mod 5=1,3,4 だと先攻勝ちになりそうだ,ということでプログラムは組む.

が,ここは時間制限なんてないので枠を広げて一般的な場合として 4 → M,つまり減らせる数が Mk だとして考える.
mod (M+1) で考えると,Mk≡-1k=±1 なので N←1 の2択の遷移を選んでいるだけである.ただし N≦M においては N←N-1 しか選択肢が無い.
別の見方をすると,N≡M≡-1 の場合は +1 させる選択肢が常に存在し,他の場合は -1 させることしかできない場合がある.
なので N≡M 以外では実質的に +1 させる選択肢が無いと考えると,自分のターンが回ってきた時 N≡n が偶数だと N≡0 への変更があり得ないので必敗である.M が偶数の場合,-n と M+1-n とで偶奇が異なるが,必勝側は必勝選択肢を取るので N≡-1 以外では +1 の選択肢は本質的に無いと考えることになる.
つまり N≡2k+1 or M であれば必勝,他は必敗である.これを元の問題に当てはめると,N≡1,3,4の場合に太郎は必勝,他は花子が必勝になる.

コード

#include <string>
using namespace std;

class PotatoGame
{
public:
  string theWinner(int n)
  {
    n %= 5;
    return (n==0||n==2)?"Hanako":"Taro";
  }
};