yukicoder No.2020 Sum of Common Prefix Length (yukicoder contest 353 G) 別解

公式解説ではクエリを全て受け取ってから前処理をしていますが、オンラインで解くことができます。

問題のリンク

問題概要

文字列が $N$ 個与えられるので、以下のクエリを $Q$ 回処理

クエリ1 : 文字列 $x$ の末尾に英小文字 $c$を加える

クエリ2 : $i=1,2,...,N$ に対して $S_{i}, S_{x}$ の最長共通接頭辞の長さを求め、それらの総和を答える。

クエリを先に受け取るときの解き方(ほぼ公式解説)

文字列の頭の部分が一緒といえば Trie木 です(?)。

Trie木についてはこれとかこれとかをみてみましょう。

今回は通常の Trie木 のノードごとにそのノード $p$ を通る文字列の数 $C_{p}$を記録しています。

すると答えは $S_{x}$ の末尾を表すノードから根っこのノード間にあるノードの $C_{p}$ の和となります。

公式解説ではクエリを先に全て受け取ることで、木上の一点加算とパス上の頂点の総和の問題に帰着しています。

別解

深さ $B$ で場合わけします。

まず、Trie木 のノードごとにそのノード $p$ を通る全て文字列の index を記録した配列 $D_{p}$ を持ちます。 また、長さ $N$ の配列 $base=(base_{1},...,base_{N})$ を用意します。(初期値は0)

$base_{x}$ というのは $S_{x}$ との最長共通接頭辞の長さが $B$ 以上の全てに対して文字列 $B$ 文字目以降で一致している文字数の総和です。

ある深さ $B$ より深いノードについてはそのノードに新しく文字列 $x$ が通過する際に($D_{p}$ に $x$ を追加する前に)以下の操作をします。

  • $D_{p}$ に含まれる全ての要素 $y$ に対して $base_{y}$ に1加算する。
  • $base_{x}$ に $|D_{p}|+1$ を加算する。

最初の文字列を追加する際やクエリ1を処理する際は以上の操作で十分です。

クエリ2を処理する際は以下のようにします。

  • まず、$ans=base_{x}$ とする。
  • 文字列 $S_{x}$ の 前 $B$ 文字が通っているノード $p$ について $ans$ に$|D_{p}|$ を加算する。

なぜ、これでいいのかというと、 $base_{x}$ というのは $B$ 文字目以降で一致している文字数の総和であるため、足りないのは $B$文字目より前で一致している文字数であるからです。

計算量

最初の文字列の長さの和を $M $ とします。

まず、クエリ2については一つ当たり $O(B)$ で処理することがでるので、全体で $O(QB)$ です。

文字を追加する操作ですが、一つ当たり最悪計算量が $O(N)$ なので、一見全体で $O((M+Q)N)$ のように見えますが違います。

例えば全ての文字列が深さ $B$ のノードを通るとき、 $N\times B \leq M+Q$ を満たす必要がありますが、これは $B$ が大きくなればなるほど困難になります。

自分の考察が間違っていなければ全体で $O(\frac{(M+Q)^2}{B})$ になるはずです。

よって、 このアルゴリズム全体では計算量が $O(\frac{(M+Q)^2}{B}+QB)$ になります。

$B$ を適切な値にすることで十分高速に通ることができます。(テストケースにこの解法に強いものが存在しているのかは分かりません)

実装例