3668 - A Funny Stone Game

http://livearchive.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=242&page=show_problem&problem=1669
Regionals 2006 :: Asia - Beijing
I問題

問題

2人で行うゲームを考える。
0〜n-1の番号がついたn個の石の山があり、それぞれの山に何個かの石が積まれている。
プレイヤーは自分の番に3つの数 i, j, k (i < j, j <= k, 山iには1つ以上の石が残っている)を決める。
その後、山iから石を1つ取り、山j,kそれぞれに石を1つずつ追加する。
これを2人が繰り返し、先にi,j,kを選べなくなったほうが負けとなる。
それぞれの山の石の数の初期値が与えられるので、先手が勝てるかどうか判定せよ。
勝てる場合、どのようなi,j,kを選べばいいのかも求めよ。
ただし複数ある場合は辞書順最小を求めるものとする。

制約

1<=n<=23
0<=石の数の初期値<=1000

解法

Grundy数。
蟻本のゲームの章の最後に載っている、分裂が起きるタイプのGrundy数が参考になる。
今、山iにある石1つに注目すると、この石の状態はn-iで表すことが出来る。
状態n-iから1回の操作で、状態n-jと状態n-kに分裂すると考えることが出来、蟻本と同様に状態n-iのGrundy数を求めることが出来る。
全体の勝敗判定をする場合は、全ての石についてのGrundy数をXORすれば出来る。
ただし、ある山の石についてXORするときは、XORの性質から石の数の偶奇で場合分けするだけでよい。
それにしてもGrundy数は狐に抓まれたような気持ちになる・・・・

ソース

int g[50];
 
int grundy(int x) {
  if (g[x] >= 0) return g[x];
  set<int> s;
  REP(i,x) {
    REP(j,i+1) {
      s.insert(grundy(i) ^ grundy(j));
    }
  }
  int res = 0;
  while(s.count(res)) ++res;
  return g[x] = res;
}
 
int a[50];
 
int main() {
  REP(i,50) g[i]=-1;
  REP(i,50) grundy(i);
  int n;
  int cs = 1;
  while(cin>>n,n) {
    printf("Game %d: ", cs++);
    REP(i,n) cin >> a[i];
    int now = 0;
    REP(i,n) if (a[i]&1) now ^= g[n-i-1];
    if (now == 0) puts("-1 -1 -1");
    else {
      REP(i,n-1) {
        if (a[i] == 0) continue;
        for (int j=i+1; j<n; ++j) {
          for (int k=j; k<n; ++k) {
            if ((now ^ g[n-i-1] ^ g[n-j-1] ^ g[n-k-1]) == 0) {
              printf("%d %d %d\n", i,j,k);
              goto next;
            }
          }
        }
      }
    next:;
    }
  }
}