問題を典型パーツに分解して考察を積み上げていく系の、とても教育的な問題!
問題概要
個の文字列
と文字列
が与えられる。以下の条件を満たす
の組の個数を求めよ。
と
をこの順に連結してできる文字列から、いくつかの文字を選び、順序を保って連結すると文字列
に一致する
制約
の長さの総和は
以下
考えたこと
この手の問題では、考察を一歩一歩積み上げることが肝要といえる。まず、次の問題が解ける必要があることに気づく
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; }