短題解:我的簡要題解。
為了簡短和易懂,我在短題解中,可能會在不約定的情況下使用不那麼規範、公認的數學語言、記號,表述有時也不完全形式化。好處是:
算了,就不自賣自誇了,看看就知道了,總之很牛!
簡要題意
洛谷上的題面已經挺簡要了: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;
}