Official

E - Joint Two Strings Editorial by leaf1415


\(S_i\) に対して、

  • \(S_i\) に部分列として含まれる \(T\) の最長の接頭辞の長さを \(A_i\)
  • \(S_i\) に部分列として含まれる \(T\) の最長の接尾辞の長さを \(B_i\)

と定め、まず数列 \(A = (A_1, A_2, \ldots, A_N)\) と数列 \(B = (B_1, B_2, \ldots, B_N)\) を計算します。

ある \(i\) について \(A_i\) を計算するには、\(S_i\)を 先頭の文字から順に見ていき、\(T\) の文字が先頭何文字まで \(S_i\) の中に現れるかを求めれば良いです。 より具体的には、

  • まず最初の目標の文字\(T\)\(1\) 文字目 \(T_1\) に設定して、\(S_i\) を先頭から順に見ていきます。

  • \(S_i\) を見ていく過程で目標の文字 \(T_1\) が現れたら、目標の文字を \(T\)\(2\) 文字目 \(T_2\) に変更して、\(S_i\) の続きを見ていきます。

  • その過程でまた目標の文字 \(T_2\) を見つけたら、目標の文字を \(T\)\(3\) 文字目 \(T_3\) に変更して、\(S_i\) の続きを見ていきます。

  • \(\cdots\)

という手順を行い、\(S_i\) を全て調べ終わるまでに、目標の文字を見つけた回数を \(A_i\) とすれば良いです。 この手順では、\(S_i\) を先頭から \(1\) 回走査するので、かかる時間は \(O(|S_i|)\) です。よって、この方法で数列 \(A\) 全体を求めるのにかかる時間は \(O(\sum_{i = 1}^N |S_i|)\) と評価できますが、\(\sum_{i = 1}^N |S_i| \leq 5 \times 10^5\) なので、十分高速です。

数列 \(B\) を求めるには、各文字列 \(S_i\)\(T\) を前後反転したのち、数列 \(A\) を求めたのと同様の処理をすれば良いです。

また、\(B_i = j\) となるような \(i\) の個数を \(C_j\) として定め、 数列 \(C = (C_0, C_1, \ldots, C_{|T|})\) を、\(O(N)\) 時間で前計算しておきます。

以上の準備をもとに本題に入ります。 \(1\) 以上 \(N\) 以下の整数のペア \((i, j)\) が問題文中の条件を満たす、つまり、\(S_i\)\(S_j\) を連結した文字列が \(T\) を部分列として含むとき、ペア \((i, j)\)良いペアと呼ぶことにし、本問題の答えである、良いペアの個数を数えましょう。

まず、\(i\) を固定したときの、\((i, j)\) が良いペアとなる \(j\) の個数を求めることを考えます。 \((i, j)\) が良いペアであることは、\(A_i + B_j \geq |T|\) と同値なので、 \((i, j)\) が良いペアとなる \(j\) は、\(B_j\)\(l_i \coloneqq |T| - A_i\) 以上であるような \(j\) です。 そして、\(B_j\)\(l_i\) 以上であるような \(j\) の個数は、数列 \(C\) を用いて \(\sum_{k = l_i}^{|T|} C_k\) と表せます。 よって、これを全ての \(i\) について足し合わせることで、本問題の答えは

\[\sum_{i = 1}^N \sum_{k = l_i}^{|T|} C_k \tag{1}\]

と得られます。上式 (1) で和をとる項の個数は

\[ \begin{aligned} \sum_{i = 1}^N (|T| - l_i + 1) = \sum_{i = 1}^N (|T| - (|T| - A_i)+ 1) = \sum_{i = 1}^N A_i + N \leq \sum_{i = 1}^N |S_i| + N \leq 5 \times 10^5 + 5 \times 10^5 \end{aligned} \]

と十分小さいので、式 (1) を、たとえば \(2\) つのシグマ記号に対応した \(2\) 重ループなどで愚直に計算しても十分高速です。

以下に、C++ 言語による正解例を記載します。

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

ll n;
string s[500001], t;
ll a[500001], b[500001], c[500001];

ll calc(const string &s, const string &t)
{
  ll g = 0;
  for(auto c : s){
    if(g >= (int)t.size()) break;
    if(c == t[g]) g++;
  }
  return g;
}

int main(void){
  cin >> n >> t;
  for(int i = 1; i <= n; i++) cin >> s[i];
  
  for(int i = 1; i <= n; i++) a[i] = calc(s[i], t);
  reverse(t.begin(), t.end());
  for(int i = 1; i <= n; i++){
    reverse(s[i].begin(), s[i].end());
    b[i] = calc(s[i], t);
    c[b[i]]++;
  }
  
  ll ans = 0;
  for(int i = 1; i <= n; i++){
    ll l = (ll)t.size() - a[i];
    for(int j = l; j <= t.size(); j++){
      ans += c[j];
    }
  }
  cout << ans << endl;
  
  return 0;
}

posted:
last update: