へなちょここーだー

プログラミング初心者がtopcoderなどの競技プログラミングに挑戦。勉強したアルゴリズムなどを書いていきます。現在topcoder緑でcodeforces青。どちらもDiv1昇格を目指しています。

Grundy数 その3

前回までにGrundy数についての説明をしました。augusuto04.hatenablog.com
augusuto04.hatenablog.com
長くなったGrundy数の記事も今回でラスト。実際に使ってみます。

実践例

AtCoder Regular Contest 038のC - 茶碗と豆に挑戦します。

#include <iostream>
using namespace std;
static const int NMAX = 100000;

//Cがラベルの番号、Aが豆の数、gはGrundy数
int N, C[NMAX], A[NMAX], g[NMAX];

int main(){
  cin >> N;
  C[0] = A[0] = g[0] = 0;
  int result = 0;
  for(int i = 1; i <= N - 1; i++){
    cin >> C[i] >> A[i];
    g[i] = 0;
    //茶碗iにおけるGrundy数を求める
    while(true){
      bool d = true;
      for(int j = 1; j <= C[i]; j++){
        if(g[i] == g[i - j]){
          g[i]++;
          d = false;
          break;
        }
      }
      if(d == true)
        break;
    }
    //全体のGrundy数を更新する
    for(int j = 0; j < A[i]; j++){
      result = result ^ g[i];
    }
  }
  if(result > 0)
    cout << "First" << endl;
  else
    cout << "Second" << endl;
}

ただし、これではNとAの値が小さい問題しか解けず、他はタイムアウトになってしまうため部分点の80点しかとれません(まぁ初心者の私にとっては80点でも取れれば大きいんですけどねw)。次の部分点を取るにはAの値が大きいときに対応する必要があります。なので、Grundy数の更新の計算量を少なくしました。

//全体のGrundy数を更新する
for(int j = 0; j < A[i]; j++){
  result = result ^ g[i];
}

上のような計算をしていました。しかし、同じ数を繰り返しxorの計算をすることに実はあまり意味がありません。
a xor a = 0
a xor 0 = a
となるため、同じ数を2回計算するときは無視します。すると次のようになります。

//全体のGrundy数を更新する
if(A[i] % 2 == 1){
  result = result ^ g[i];
}

つまり、偶数回(0回も含む)更新するときは値が変わらないため行わない。奇数回更新するときは1回更新することと同じである。この変更を行うことで100点取れました!ただ、満点は104点です。どうやらg[i]の求め方を工夫する必要があるみたいですね。私はまだ出来ていません。そのやり方やコードの改善等あったら教えていただけると有り難いです。