区間[l,r]に登場する同じ数をまとめてそれぞれ個数を数える。grundy数を考えると、ゲームの勝敗は個数の組み合わせだけで決まっていることがわかる。つまり、現れる個数をs1,...,sk個とした時に、その総xor S=s1⊕s2⊕...⊕skで勝敗が決まっている。先手が初手でsiを減らしてsi⊕Sにすることができればxorがゼロになって、先手必勝の盤面になる。したがって、先手が勝てる初手の総数は
(s1⊕Ss1)+ ...+(sk⊕Ssk)
と計算できる。ただし、石を増やしたりパスしたりすることはできないのでsi≤si⊕Sの項は0とする。これを素朴に計算すれば部分点が取れる。
満点解法について。個数が同じもの、つまりsi=sjとなるようなものはまとめて計算できることに注目する。つまり、個数siがc(si)個あると考えるとき、c(si)を係数として
c(si)(s1⊕Ss1)+...+c(sk)(sk⊕Ssk)(i=jならsi=sjとする)
と計算できる。登場する個数がl種類あるとして、lがもっとも大きくなるのは1回登場する数が1種類、2回登場する数が1種類、...、l回登場する数が1種類あるときで、このとき
1+2+...+l≤N
なので、l=O(N)と評価できる。これなら全体の計算量はO(QN)となって間に合うはず。
さて、問題は区間[l,r]に登場する数をどのように数えるかだが、Mo's algorithmでできるのでそうした。
- size[x]:= ある区間に登場する数xの個数
- coef[y]:= 個数yにかかる係数。
というデータを持ちながら伸縮すればO(1)で遷移できるので、Q個のクエリに対しては O(NQ) となる。つまり、計算量をまとめると O(QN+NQ)。
ただ、sizeとcoefをハッシュテーブルで持つと定数倍が重すぎて間に合わなかった。sizeに関しては、登場する数を座標圧縮すれば長さN以下の配列で持てる。しかし、coefは O(N) で走査する必要があるので、素朴に配列を使うと方針が破綻する。これは双方向リストっぽい持ち方で解決した。つまり、 長さNの配列で持った上で、
- next[y]:= t>yでcoef[t]>0となるような最小のt
- prev[y]:= t<xでcoef[t]>0となるような最大のt
という風に前後のインデックスを保持しておく。coefのある項を増やしたり減らしたりするときにはnextとprevを適切に変更する。こうすると、先頭からnextをたどればcoefの正の要素だけを走査できるようになる。これで間に合った。
SBCLについて。これでも11秒くらい掛かって不思議に思っていたのだが、原因はコード変換に失敗していたことだった。sb-c:define-source-transform
はdefine-compiler-macro
と違ってコンパイル時実行ではないので、eval-when
で括らないとコンパイル時には存在しないことになってしまう。直したら余裕で間に合った。AtCoderだとスクリプト実行だからこの問題が起きなくて気づけなかった。