F - LCS

F - LCS

キーワード

解説

問題 (opens in a new tab)

sstt を左から見ていき、一致する文字を貪欲に取ることで、最長共通部分列を求めることができる。

m=s,n=tm = |s|, n=|t| として、 i=0,1,,m, j=0,1,,ni=0,1,\dots,m, ~ j=0,1,\dots,n に対して、 dpi,jdp_{i,j} を次のように定義する。

ss の最初の ii 文字と tt の最初の jj 文字の最長共通部分列の長さ

ss の最初の ii 文字と tt の最初の jj 文字の最長共通部分列の長さは、

  • ssii 文字目と ttjj 文字目が一致する場合、 ss の最初の i1i-1 文字と tt の最初の j1j-1 文字の最長共通部分列の長さに 11 を足したもの
  • そうでない場合、 ss の最初の i1i-1 文字と tt の最初の jj 文字の最長共通部分列の長さと ss の最初の ii 文字と tt の最初の j1j-1 文字の最長共通部分列の長さの大きい方

となる。よって、次の漸化式が成り立つ。

dpi,j={dpi1,j1+1(si=tj)max{dpi1,j,dpi,j1}(sitj)dp_{i,j} = \begin{cases} dp_{i-1,j-1} + 1 & (s_i = t_j) \\ \max\{dp_{i-1,j}, dp_{i,j-1}\} & (s_i \neq t_j) \end{cases}

計算量は一回の遷移に O(1)O(1) かかるので、全体で O(mn)O(mn) となる。

最終的な答えは dpm,ndp_{m,n} となる。

復元

最長共通部分列を復元するためには、 dpm,ndp_{m,n} から逆順に遡っていく。

  • dpi,jdp_{i,j}dpi1,jdp_{i-1,j} と等しいまたは dpi,j1dp_{i,j-1} と等しい場合、 dpi,jdp_{i,j}dpi1,jdp_{i-1,j} または dpi,j1dp_{i,j-1} から遷移してきたことになる。この場合、 sis_itjt_j は共通部分列に含まれない。
  • そうでない場合、 si,tis_i,t_i を共通部分列の先頭に追加し、 dpi1,j1dp_{i-1,j-1} に遷移する。

これを dp0,0dp_{0,0} に到達するまで繰り返すことで、最長共通部分列を復元できる。

提出コード

int main() {
    string s, t; cin >> s >> t;
    vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
    rep(i,1,s.size()+1) {
        rep(j,1,t.size()+1) {
            if (s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
            dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
        }
    }
    int i = s.size(), j = t.size();
    string ans = "";
    while (i > 0 && j > 0) {
        if (s[i-1] == t[j-1]) {
            ans = s[i-1] + ans;
            i--; j--;
        } else if (dp[i-1][j] > dp[i][j-1]) i--;
        else j--;
    }
    cout << ans << endl;
}