Official

E - カードの消去ゲーム / Card Elimination Game Editorial by MtSaka


この問題においては操作後に残るカードを左から見たときに印字される文字が文字列 \(T\) と一致していることのみが条件です。なぜならば文字列を一致させることが可能なのであればその各位置に適切に整数を書き込むことで \(Q\) に一致させることが必ず可能であるからです。

したがって、以下のような動的計画法が考えられます。

\(\text{dp}[i][j]=i\) 枚目までのカードで\(T\) と \(j\) 番目まで一致している状態にすることが可能かどうか

このテーブルを計算した後、\(\text{dp}[N][L]\) が真ならばYes、そうでないならばNo と出力すれば正解が得られることになります。

遷移を考えます。\(\text{dp}[i][j]\) が真ならば、 \(S_{i+1}=T_{j+1}\) であれば \(\text{dp}[i+1][j+1]\) を真にしてよいです。また、\(\text{dp}[i][j]\) が真かつ \(i+1\) 枚目から \(k\) 枚目 \((k>i)\) までをすべて取り除けるならば \(\text{dp}[k][j]\) を真にすることができます。 これらを \((i,j)\) の昇順に遷移していくことでこの dp テーブルを計算できます。

ここで、遷移の途中で出て来る \(i+1\) 枚目から \(k\) 枚目をすべて取り除けるかという部分問題を解く必要があります。 こちらもまた動的計画法で解くことができます。 \(\text{can}[l][r]\) を \(l\) 枚目から \(r\) 枚目を取り除けるかというのを判定するとき、\(l\) 枚目がどのカードと隣あって取り除かれたのかを探索します。 \(l<i \leq r\) を満たすある \(i\) について \(P_l=P_i\) かつ \(\text{can}[l+1][i-1]\) かつ \(\text{can}[i+1][r]\) であれば、\(\text{can}[l][r]\) も真になります。これを \(r-l\) 、つまり区間の長さが小さい順に計算していくいわゆる区間 dp を行うことで正しい遷移で計算できます。

したがって、全体で時間計算量 \(\mathrm{O}(N^3)\) で解くことができます。

実装例(C++)

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, m, l;
    cin >> n >> m >> l;
    string s, p, t;
    cin >> s >> p >> t;
    vector<int> q(l);
    for (auto& e : q) cin >> e;
    vector<vector<int>> can_erase(n + 1, vector<int>(n + 1, 0));
    for (int i = 0; i < n + 1; ++i) can_erase[i][i] = 1;
    for (int len = 2; len <= n; len += 2) {
        for (int l = 0; l + len <= n; ++l) {
            int r = l + len;
            for (int i = l + 1; i < r; ++i) {
                bool flag = true;
                flag &= (p[l] == p[i]);
                flag &= (can_erase[l + 1][i]);
                flag &= (can_erase[i + 1][r]);
                if (flag) {
                    can_erase[l][r] = 1;
                    break;
                }
            }
        }
    }
    vector<vector<int>> dp(n + 1, vector<int>(l + 1, 0));
    dp[0][0] = 1;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < l; ++j)
            if (s[i] == t[j] && dp[i][j]) dp[i + 1][j + 1] = 1;
        for (int r = i + 1; r <= n; ++r) {
            if (!can_erase[i][r]) continue;
            for (int j = 0; j < l + 1; ++j) dp[r][j] |= dp[i][j];
        }
    }
    if (dp[n][l])
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}