2020年5月11日月曜日

CodeChef May Challenge 2020 - Buying a New String

たくさん解かれているのに部分点すらわからないので、書いたことのない文字列アルゴリズムだろうなと検討をつける。Aho-Corasickがいちばんそれっぽいので、貼るととりあえず部分点が取れた。

AB|A||B|個の文字列間で遷移の共通部分が多いことを利用できれば満点が取れそう。そして、そのためにはAho-Corasickをちゃんと理解する必要がありそう。しかし、ei1333さんのコードを見ていても理屈がよくわからないので、明らかな性質だけでなんとかすることにした:

  • Aho-Corasickのマッチは部分文字列の一致をひとつずつカウントする。つまり、まとめてカウントしたりはできないので、素朴にやるとマッチする文字列の長さに対してO(N2){O}(N^2)になる。とりあえず、トライ木を作ったらノード毎に重みの和をまとめておいたほうがよさそう。たぶんそれでオーダーも落ちる。
  • Aho-Corasickの状態は今いるノードだけで決まる。つまり、ノードが同じで以降の文字列が同じならその後の遷移はすべて同じ。

以上に基づくと、次の配列を作っておけばよいはず:

cumulA[i]:=A\operatorname{cumul}_A[i] := Aの区間[0,i)[0, i)を走査した後のスコア
nodeA[i]:=A\operatorname{node}_A[i] := Aの区間[0,i)[0, i)を走査した後のノード
cumulB[i]:=B\operatorname{cumul}_B[i] := Bの区間[0,i)[0, i)を走査した後のスコアを00として、そこから最後まで走査して得られるスコア。
nodeB[i]:=B\operatorname{node}_B[i] := Bの区間[0,i)[0, i)を走査した後のノード

A,BA, Bをそれぞれ[0,i1,),[i2,length(B))[0, i_1,), [i_2, \operatorname{length}(B))で切った時のスコアはcumulA[i1]\operatorname{cumul}_A[i_1]nodeA[i1]\operatorname{node}_A[i_1]からB[i2,length(B))B[i2, \operatorname{length}(B))を走査して得られるスコアを足したものになるが、この途中で、BBの位置iiを走査する際にノードがnodeB[i]\operatorname{node}_B[i]と一致していることが確認できたらcumulB[i]\operatorname{cumul}_B[i]を足して打ち切ってよい。単語の長さが高々26なので、i1i_1からそれだけ遷移すれば一致するはず。計算量はたぶんアルファベットの数をα\alphaとしてO(αAB+iSi){O}(\alpha|A||B| + \sum_i S_i)


そしてまだAho-Corasickを理解していない……