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数の一見不規則に見えて実は美しい規則性、計算量の落とし方の工夫、ともに面白い問題だった。