G - Count Substring Query Editorial
by
MMNMM
\(S\) および \(T _ i\) に含まれるどの文字より辞書順で小さな文字 `
と、辞書順で大きな文字 {
, }
({
\({}\lt{}\)}
) を加えた文字集合 \(\Sigma\) の文字列を考えます( \(\sigma=|\Sigma|\leq29\) です)。
文字列 \(C\) を \(C=S+{}\){
\({}+T _ 1+{}\)`
\({}+T _ 1+{}\)}
\({}+T _ 2+\cdots+{}\)}
\({}+T _ Q+{}\)`
\({}+T _ Q+{}\)}
として定めます。
\(C\) は長さ \(L=\displaystyle|S|+2\sum _ {i=1} ^ Q|T _ i|+2Q+1\) の文字列です。
\(C\) の Suffix Array の値 \((\operatorname{SA} _ C[i]) _ {1\leq i\leq L}\) に対して、処理すべきクエリは以下のようになります。
- \(T _ i+{}\)
`
の先頭を \(C\) の \(L _ i\) 文字目、\(T _ i+{}\)}
の先頭を \(C\) の \(U _ i\) 文字目とする。\(\operatorname{SA} _ C\) で \(L _ i\) と \(U _ i\) に挟まれた値のうち、値が \(|S|\) 以下のものはいくつあるか?
これは、事前に \(L _ i,U _ i\) を求めておき、\(C\) の Suffix Array を先頭からみることで解くことができます。
Suffix Array は文字列長に対して線形時間で構築できるので、時間計算量は \(O(|S|+\sum |T _ i|+\sigma)\) などでこの問題を解くことができます。
実装例は以下のようになります。
以下の実装例では \(L _ i,U _ i\) から \(i\) を求める際にハッシュテーブルを用いているため空間計算量が \(O(Q)\) ワードで時間計算量が expected \(O(|S|+\sum |T _ i|+\sigma)\) となっていますが、追加で \(O((|S|+\sum |T _ i|)\log Q)\) bit 程度の空間を使うことで時間計算量を worst にすることもできます。
#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <atcoder/string>
int main() {
using namespace std;
string S;
cin >> S;
unsigned N(size(S)), Q;
cin >> Q;
// Li, Ui から i を求める
unordered_map<unsigned, unsigned> concatenated_idx_to_query;
// 与えられる文字列を連結してできる列(連結する際に値を 0 以上 28 以下になおす)
vector<int> concat;
// S + { を追加
ranges::transform(S, back_inserter(concat), [](auto c){return c - '`';});
concat.emplace_back(27);
for(unsigned i{}; i < Q; ++i){
string T;
cin >> T;
// Li をメモ
concatenated_idx_to_query[size(concat)] = i;
// Ti + ` を追加
ranges::transform(T, back_inserter(concat), [](auto c){return c - '`';});
concat.emplace_back();
// Ui をメモ
concatenated_idx_to_query[size(concat)] = i;
// Ti + } を追加
ranges::transform(T, back_inserter(concat), [](auto c){return c - '`';});
concat.emplace_back(28);
}
vector<unsigned> ans(Q);
// counter := これまでに出てきた S の部分文字列に対応するインデックスの個数
for(unsigned counter{}; const auto idx : atcoder::suffix_array(concat, 28)){
if(idx < N)
++counter;
if (concatenated_idx_to_query.contains(idx))
(ans[concatenated_idx_to_query[idx]] *= -1) += counter;
}
for(const auto a : ans)
cout << a << '\n';
cout << endl;
return 0;
}
posted:
last update: