短題解:我的簡要題解。

為了簡短和易懂,我在短題解中,可能會在不約定的情況下使用不那麼規範、公認的數學語言、記號,表述有時也不完全形式化。好處是: 算了,就不自賣自誇了,看看就知道了,總之很牛!

簡要題意

洛谷上的題面已經挺簡要了:CF1924A Did We Get Everything Covered?,同時沿用其變量名與記號。

思路

設我們嘗試構造的反例串為 \(t\)。當然,可能不存在反例,所以説是嘗試。

:::info[提示 1]
回憶如何利用序列自動機判定:\(t\) 是否是 \(s\) 的子序列。
:::

:::info[提示 2]
想要儘可能地滿足/不滿足判定?

這類問題常可以轉化為:最優化參與決定判定結果的一些變量。
:::

:::info[提示 3]
這個量在此問題中,可以是自動機上的結點指針。

以此來貪心決定 \(t\) 的第一個字符吧。
:::

:::info[提示 4]
決定完第一個字符後,可以轉化為子問題嗎?
:::

:::success[解]
使得不為子序列的充要條件,是把結點指針移出 \(s\)

因此每次選擇一個,滿足下一步選擇它後,轉移到的下標是最大的,字符,加入 \(t\)

若發現在決定完 \(t\) 的第 \(i(i \leq n)\) 個字符時結點指針已經移出,那麼 \(t\) 的 \([i + 1, n]\) 個字符可以任意填充,並報告 \(t\) 不是 \(s\)

否則 \(s\) 一定合法。
:::

代碼

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

constexpr int N = 1000 + 1;

int nxt[N][26];

void solve() {
    int n, k, m;
    string s;
    cin >> n >> k >> m >> s;
    fill_n(nxt[m], k, m);
    for (int i = m - 1; ~i; --i) {
        memcpy(nxt[i], nxt[i + 1], sizeof(int) * k);
        nxt[i][s[i] - 'a'] = i;
    }
    int p = 0;//結點指針
    string t;
    for (int i = 1; i <= n; ++i) {
        int itr = max_element(nxt[p], nxt[p] + k) - nxt[p];//跳得儘量遠
        t += itr + 'a';
        if (nxt[p][itr] >= m) {
            cout << "NO\n" << t + string(n - i, 'a') << '\n';//隨便填,但注意有字符集限制
            return ;
        }
        p = nxt[p][itr] + 1;
    }
    cout << "YES\n";
}

int main() {
    ios::sync_with_stdio(false);
  	cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
      solve();
    }
  	return 0;
}