Baby Stepsなブログ

競プロとか。間違ったこと書いてあったら@pi0nep1oneにご連絡ください。

ABC 185 E - Sequence Matching 解説

問題

atcoder.jp

前提

解説

最長共通部分列問題(Longest Common Subsequence)が解法の基盤になっていると思った。

LCSのDPでは、複数の列における部分列の長さの最大値を管理するが、この問題では不一致の部分を残した方がスコアが良い場合があるので、それを考える必要がある。

例) A={1,2,9,9,3}, B={1,2,8,8,3} LCSは{1,2,3} で、LCS以外を全て除外する場合のスコアは4だが、そのまま残しておけばスコアは2になる

よって以下の様にDPテーブルを定義して、ある要素を除外する場合と残す場合それぞれの遷移を考えて更新していく。

dp[i][j] := 数列Aの先頭i、数列Bの先頭jまで見た時のスコアの最小値

まずi=0 or j=0 という場合は、長さを一致させるために片方の文字列は全消しするしかないので、この場合は、dp[i][j] = max(i, j)

これがテーブルの初期化に対応する。

i, jがいずれも1文字以上の場合の遷移は2通りに分けて考える。

  • A[i] == B[j] の場合

dp[i+1][j+1] = dp[i][j] + 0 // i-1, j-1までの部分列の末尾に同じ文字を追加してもスコアは変わらない

  • A[i] != B[j] の場合

ここで更に末尾の要素を残すか、残さないか2通りの遷移を考える必要がある。

  1. dp[i+1][j+1] = dp[i][j] + 1 // i-1, j-1 までの部分列の末尾にA[i], B[i]を残す。スコア+1
  2. dp[i+1][j+1] = min(dp[i][j + 1], dp[i + 1][j]) + 1 // 少なくとも片方の末尾を除外する場合。A側を除外する場合、B側を除外する場合の小さい方のスコアに+1

実装

#include <bits/stdc++.h>
using namespace std;

#define rep(i, n) for (long long i = (long long)(0); i < (long long)(n); ++i)
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

const int INF = 1e9;

signed main() {
  int n, m;
  cin >> n >> m;
  vector<int> a(n), b(m);
  rep(i, n) cin >> a[i];
  rep(i, m) cin >> b[i];

  vector<vector<int>> dp(n + 1, vector<int>(m + 1, INF));
  rep(i, n + 1) dp[i][0] = i;
  rep(i, m + 1) dp[0][i] = i;
  rep(i, n) {
    rep(j, m) {
      if (a[i] == b[j]) {
        chmin(dp[i + 1][j + 1], dp[i][j]);
      } else {
        chmin(dp[i + 1][j + 1], dp[i][j] + 1);
        chmin(dp[i + 1][j + 1], min(dp[i][j + 1], dp[i + 1][j]) + 1);
      }
    }
  }
  cout << dp[n][m] << endl;
}