ABC477 C - Range Search Query

範囲検索クエリ

考え方

まず、愚直にやるのは簡単。
ただし、それでは計算量が $O(QN|T|)$ となりTLEする。

そこで、メモ化を行う。
つまり、事前に $T$ の文字列がある位置の先頭を全て愚直な全探索で拾っておく。
すると、$L$ 以上 $R-|T|+1$ 以下の数がこの中にあるかどうかという問題になる。

これは、該当位置を $1$、そうでない位置を $0$ とする数列の累積和を利用することで、毎回 $O(1)$ で求まる。

よって、全体の計算量 $O(N|T|+Q)$ で解ける。

入力例1での動作

入力を受け取る。

q: 3
s: "abcdabc"
t: "bc"

$S$ の中で $T$ が始まる位置を調べる。
$|T|=2$ なので、スタート位置として調べるのは $1$ 文字目から $6$ 文字目までである。

各スタート位置から $2$ 文字を取り出し、$T$ と一致するかを確認する。
一致するなら $1$、そうでなければ $0$ とする。

スタート位置 $1$ $2$ $3$ $4$ $5$ $6$
$S$ の部分文字列 "ab" "bc" "cd" "da" "ab" "bc"
$T$ と一致するか いいえ はい いいえ いいえ いいえ はい
値 $0$ $1$ $0$ $0$ $0$ $1$

したがって、$T$ が始まる位置に対応する列は $0,1,0,0,0,1$ となる。

この列の先頭に $0$ を $1$ つ置き、左から累積和を取る。
先頭から $k$ 個の合計は、次のようになる。

$k$ $0$ $1$ $2$ $3$ $4$ $5$ $6$
$k$ 文字目の値 - $0$ $1$ $0$ $0$ $0$ $1$
先頭 $k$ 個の累積和 $0$ $0$ $1$ $1$ $1$ $1$ $2$

例えば、スタート位置 $2$ 以上 $5$ 以下にある $1$ の個数を考える。
先頭 $5$ 個の累積和から、先頭 $1$ 個の累積和を引けばよい。
この場合は $1-0=1$ となり、範囲内に $T$ のスタート位置があるとわかる。

各クエリについて、$T$ のスタート位置として使える範囲を確認する。

クエリ スタート位置の範囲 累積和の差 答え
$[2,6]$ $[2,5]$ $1-0=1$ Yes
$[3,5]$ $[3,4]$ $1-1=0$ No
$[3,7]$ $[3,6]$ $2-1=1$ Yes

したがって、出力は順に Yes, No, Yes となる。

注意点

添字範囲がかなり複雑になる。

$T$ のスタート位置として採用可能なのは、$N$ 番目までではなく、$N-|T|+1$ 番目の文字までである。
このこと自体がややこしい上に、$N-|T|$ ではなく $+1$ が必要なのがさらにややこしい。
しかも、$T$ が $S$ より長い可能性すら、制約では否定されていない。

加えて、クエリで来る $L$ や $R$ が、どうみても $L$ の後ろや $R$ の前に $|T|$ 文字ない場合もある。
これらを全て例外処理しなければならないので、かなり大変。

別解

本来はD問題レベルなのだが、二分探索を使った方が、気を使うところが少なくて簡単。
スタート範囲は気をつけなければいけないが、それ以外は全て注意点を無視できる。

しかも累積和を取らなくてもよいのもうれしい。
「別解」のコード参照。