問題を典型パーツに分解して考察を積み上げていく系の、とても教育的な問題!
問題概要
個の文字列 と文字列 が与えられる。以下の条件を満たす の組の個数を求めよ。
- と をこの順に連結してできる文字列から、いくつかの文字を選び、順序を保って連結すると文字列 に一致する
制約
- の長さの総和は 以下
考えたこと
この手の問題では、考察を一歩一歩積み上げることが肝要といえる。まず、次の問題が解ける必要があることに気づく
2 つの文字列 が与えられる。 からいくつかの文字を選び、順序を保って連結すると文字列 に一致させられるかを判定せよ
実はこの問題にはよく知られた Greedy 解法がある。
の「次に来る文字」を表す window を持っておき、 を前から順にみていって、window の文字に一致したら、window を の次の文字にセットしなおすようなことをすればよい。そして、 が最後まで行き着く前に、window が の最後の文字までクリアすれば "Yes"、クリアできなければ "No" だ。
こんな感じに実装できる。
bool ok = false; int iter = 0; // T の待ち文字を表す window for (int i = 0; i < S.size(); ++i) { if (S[i] == T[iter]) ++iter; // T のラス文字までクリアしたら if (iter == T.size()) { ok = true; break; } }
今回の問題へ
同様に、2 つの文字列 の部分列で を作れるかどうかは次のように判定できる。
- と を前から見ていったときに、 の前から 文字分までで一致させられるとする
- と を後ろから見ていったときに、 の後ろから 文字分まで一致させられるとする
- このとき、 が の長さ ( とする) 以上であれば、Yes と判定できる
これを踏まえると、もとの問題は次のように言い換えられる。
2 つのサイズ の数列 と が与えられる。
であるような組 の個数を求めよ。
ここまで来れば、いつもの二分探索問題となる。 と小さい順にソートしておくことで、各 に対して、 となる の個数は lower_bound()
で求められる。
計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; int main() { int N; string T; cin >> N >> T; vector<int> left(N, 0), right(N, 0); for (int i = 0; i < N; ++i) { string S; cin >> S; for (int j = 0; j < S.size(); ++j) { if (left[i] < T.size() && S[j] == T[left[i]]) { ++left[i]; } if (right[i] < T.size() && S[S.size()-1-j] == T[T.size()-1-right[i]]) { ++right[i]; } } } int M = T.size(); // left[i] + right[j] >= M となる (i, j) の個数 long long res = 0; sort(right.begin(), right.end()); for (int i = 0; i < N; ++i) { auto it = lower_bound(right.begin(), right.end(), M-left[i]) - right.begin(); long long tmp = N - it; res += tmp; } cout << res << endl; }