Official

E - Stamp Editorial by en_translator


Let us denote by \(S_i\) the \(i\)-th character (0-based) of a string \(S\).

Viewing the operations in the reversed order, we can interpret the problem as follows:

Determine if one can make all the characters of \(S\) # by repeatedly applying the following operation against \(S\):

  • Choose an \(i\ (0 \leq i < N-M+1)\) such that “\(S_{i+j}=T_j\) or \(S_{i+j}= \)# for all \(j\ (0 \leq j < M)\), and replace each of \(S_i, S_{i+1}, \dots, S_{i+M-1}\) with #.

We say \(i\) is “good” if “\(S_{i+j}=T_j\) or \(S_{i+j}= \)# for all \(j\ (0 \leq j < M)\). We refer to the operation of replacing each of \(S_i, S_{i+1}, \dots, S_{i+M-1}\) with # as simply “performing operation for \(i\).”

This new problem can be solved by a simple greedy algorithm as follows:

  1. Find a good \(i\) for which an operation is not performed yet.
  2. If there is such an \(i\), perform operation for that \(i\), and return to 1.. Otherwise, determine the answer by checking if all the characters of \(S\) are #.

This is justified because performing operation for the same \(i\) twice is meaningless, and if a good \(i\) is found, performing operation for it does not change the answer.

However, one cannot spend \(O(N)\) time every time you find a good \(i\). So, we consider the following algorithm:

  1. Prepare a queue \(Q\). Push all the good \(i\)’s in the initial state into \(Q\).
  2. While \(Q\) is not empty, repeat the following:
    • Let \(i'\) be an element popped from \(Q\). Perform operation for \(i'\), and push all \(i\) that has become good by this operation into \(Q\).
  3. Finally, check if all the characters in \(S\) has become #.

An element is popped from the queue at most \(N\) times, and an operation for \(i'\) affects to the goodness of the following \(O(M)\) number of \(i\)’s: \(i'-M+1, i'-M+2, \dots, i'+M-1\). Thus, the problem can be solved in a total of \(O(NM^2)\) or \(O(NM)\) time.

Sample code (C++):

#include<bits/stdc++.h>

using namespace std;

int main() {
    int n, m;
    string s, t;
    cin >> n >> m >> s >> t;
    vector<bool> used(n - m + 1);
    queue<int> q;
    auto check = [&](int i) {
        if (used[i]) return;
        bool ok = true;
        for (int j = 0; j < m; j++) {
            ok &= (s[i + j] == '#' or s[i + j] == t[j]);
        }
        if (ok) {
            used[i] = true;
            q.push(i);
        }
    };
    for (int i = 0; i < n - m + 1; i++) check(i);
    while (!q.empty()) {
        int i = q.front();
        q.pop();
        for (int j = 0; j < m; j++) s[i + j] = '#';
        for (int j = max(i - m + 1, 0); j <= min(i + m - 1, n - m); j++) {
            check(j);
        }
    }
    cout << (s == string(n, '#') ? "Yes" : "No") << endl;
}

posted:
last update: