Ex - XOR Sum of Arrays Editorial by maspy

解説

nimber を使わない有限体の構成などについて触れています。nimber より一般的かつ、おそらく理解・実装とも簡単です。

その他の部分はだいたい公式解説と同じ方法だと思います。


Rolling Hash

\(K\) を環とします(環とは)。 以下簡単のため、\(K\) の加法・乗法の計算量を \(O(1)\) とします。

\(x\in K\) をランダムにとり固定します。\(K\) の元の列 \(A = (A_0, \ldots, A_n)\) に対して、ハッシュ \(H_x(A) = \sum_{i=0}^n A_ix^{n-i}\) を Rolling Hash といいます。

このハッシュは、長さ \(N\) の列に対して \(O(N)\) 時間の前計算を行うことで、任意の連続部分列のハッシュを \(O(1)\) 時間で計算できる点が優れています。


加法性

\(A = (A_0, \ldots, A_n)\) および \(B = (B_0, \ldots, B_n)\) に対して \(A+B := (A_0+B_0,\ldots,A_n+B_n)\) と定めるとき、\(H_x(A+B) = H_x(A)+H_x(B)\) が成り立ちます。証明は容易です。


衝突確率

\(K\) がさらに体である場合には、次が成り立ちます。

相異なる\(A = (A_0, \ldots, A_n)\) および \(B = (B_0, \ldots, B_m)\) に対して、\(H_x(A)=H_x(B)\) が成り立つような \(x\) は高々 \(\max(n,m)\) 個である。したがって、\(x\in K\) をランダムにとってハッシュ関数 \(H_x\) を設計するとき、\(H_x(A) = H_x(B)\) となる確率は \(\max(n,m)/|K|\) 以下である。

証明は、因数定理より体において \(d\) 次方程式が \(d\) 個以下しか解を持たないことから分かります。 関連:Schwartz–Zippel lemma


本問の解法

\(K\) を、次を満たす体とします。

  • \(0, 1, \ldots, 10^{18} \in K\)
  • 加法が排他的論理和 \(\oplus\) と一致する

この体 \(K\) とランダムな \(x\in K\) からハッシュ関数 \(H_x\) を構成することにより、\(A[a:b] \oplus A[c:d]\)\(A[e:f]\) の LCP の計算、辞書順比較ができます。

\(K\) の演算の時間計算量を \(M\) とするとき、問題全体の計算量は \(O(M(N+ Q\log N))\) となります。


\(K\) の構成

加法が \(\oplus\) と一致するような体の構成は、いくつかの方法があります。公式解説で扱われている nimber はその選択肢のひとつです。が、個人的には体の構成という目的に限れば、nimber は過剰に難解であるように思います。

ここでは次の構成方法を紹介します。

  • \(\mathbb{Z}/2\mathbb{Z}\)\(\mathbb{F}_2\) と書く。
  • 多項式環 \(\mathbb{F}_2[x]\) を考え、\(d\) 次既約多項式 \(f \in \mathbb{F}_2[x]\) をとる。
  • \(K = \mathbb{F}_2[x] / (f)\) とする。これは \(2^d\) 元からなる体である。

非負整数 \(\sum_{0\leq i<d} a_i2^i\)\(a_i\in \{0,1\}\))を \(\sum_{0\leq i<d} a_ix^i \in K\) に対応させることで、\(K = \{0,1,\ldots,2^d-1\}\) として扱うこともできて、この場合 \(K\) の加法は \(\oplus\) と一致します。

この構成において唯一自然に行えない操作は「既約多項式 \(f\) をとる」部分です。ここでは省略しますが、任意の \(d\) に対して \(d\) 次既約多項式 \(f\) は存在します。\(f\) の選択肢はたくさんあり、実はどの \(f\) をとっても得られる体は同型です。実装のためには適当な数表を参照すればよくて、例えば Conway Polynomial のテーブル

http://www.math.rwth-aachen.de/~Frank.Luebeck/data/ConwayPol/CP2.html

が参照できます。\(K\) での乗算を行うには \(\mathbb{F}_2[x]\) の乗算および \(f\) での剰余ができればよく、(おそらく nimber よりもかなり)簡単です。\(O(d)\) 回のビット演算でできる他、適当な lookup table を用意することによる高速化も可能です。


参考:有限体

任意の素数 \(p\) および \(d>0\) に対して、\(p^d\) 元からなる体が存在し、それらはすべて \(\mathbb{F}_p[x] / (f)\) の形で構成できます。さらに \(p^d\) 元体同士はすべて同型であることも知られています。

posted:
last update: