ARC091 F - Strange Nim
面白い問題だったので記録。公式解説とはちょっと違う解法。
考察・解法
実験と観察
山同士は相互に干渉しないので、それぞれの山のGrundy数を求めることが目的になる。
注目している1つの山について、残っている石の数を 、その山が持つパラメータ
を単に
、Grundy数を
と記す。「もう操作できない状態」のGrundy数が0であるとして、
のとき、
。
のとき、
は
から
までに登場しない最小の非負整数。
である。
これがどのような値になるか実験してみる。そうすると、
が変化しない範囲では、
は
から
までの値がある順序で繰り返される。例えば
の範囲では、
は
の繰り返しになっている。
が
の倍数のとき
となり、それまでの順序のある位置に
が「挿入」される形で、それ以降はまた値が繰り返される。
というような性質がある。この性質を使えば、いくつかの方法でGrundy数が求められそうである。
求め方1:再帰で求める
1つ目の方法は、繰り返しの性質を用いた再帰である。 が
の倍数でないとき、以下のように考えることができる。

この考え方から、 が
の倍数でないとき
であることが分かる。
から始めて、これを
になるか
が
の倍数になるまで再帰的に繰り返すことで、
を求めることができる。
この操作1回で、 はおよそ
倍になる。
が小さいとこれで十分間に合うが、
が大きいときにはいくら指数的に減っていくとはいえスピードが遅すぎて間に合わない。
計算量はおそらく であり、
の中身を単純計算すると
のときにおよそ
となる。後で
が掛かることを考えると、
くらいが限度だろうと思われる。
※公式解法では、後述の「求め方2」は使わず、この求め方1をベースにさらに高速化の工夫をしている。
求め方2:順序への「挿入」をシミュレートする
求め方1の欠点を補うため、 が大きい時に使えるGrundy数の求め方を考える。
先述のように、 の遷移はある順序で繰り返され、
が
の倍数になるごとに、その順序のある位置に新しい値が挿入される。これを全てシミュレートしてやることでも
を求めることができる。
やりたいのは、数列のどこかのインデックス( 番目)へ値を挿入すること。そして、最後にどこかのインデックスの値を取ること。これは平衡二分木を使えば、要素数を
として
で実現できる。
この操作は全部でおよそ 回行われるので、計算量は
である。これは求め方1とは逆に
が大きいほど計算量が少なくなり、
くらいであれば十分高速である。
解法まとめ
- 山ごとにGrundy数を求める。このとき、
の値によって2通りの求め方を使い分ける。
が小さい場合、再帰で求める。
が大きい場合、順列への値の挿入を平衡二分木でシミュレートして求める。
- 全ての山のGrundy数のXORを取り、勝敗を判定する。
ACコード
Submission #3462224 - AtCoder Regular Contest 091
求め方を切り替える閾値の設定が難しく、 で分けた場合は TLEした。求め方1は処理がとても単純なので定数倍が軽く、求め方2では平衡二分木の処理が重いので、単純に
の中身を計算して比較したときの閾値より高めにするのがよい。想定解法ではないので、どう調整しても通らないときは仕方がない。
さいごに
一番最後はデータ構造で「殴って」しまったけれど、Grundy数の一見不規則に見えて実は美しい規則性、計算量の落とし方の工夫、ともに面白い問題だった。