【子序列自动机+脑筋急转弯】codeforce 2014 E. Unpleasant Strings
题目
https://codeforces.com/problemset/problem/2104/E
题解
将该问题分解为两个子问题:
- 如何快速(从左到右)找到最短子串,满足该子串可以匹配出子序列?
- 如何在匹配完子序列后的子串,计算出不存在的最短子序列长度?
对于子问题 1,可以考虑将 \(26\) 个字母的下标分别放在一个桶里,然后二分匹配出子序列。若匹配不到,则说明字符串 \(t\) 自身就不是字符串 \(s\) 的子序列,答案为 \(0\);若完全可以匹配,则需要思考子问题 2。
对于子问题 2,可以参考问题:https://leetcode.cn/problems/shortest-impossible-sequence-of-rolls/description/
不妨思考,一个字符串能匹配只包含前 \(k\) 个字母的长度为 \(len\) 的任意子序列满足具备什么性质?
将长度为 \(len\) 的子序列划分为 \(len\) 个部分:

想要让任意只包含前 \(k\) 个字母的长为 \(len\) 的子序列,都能在一个字符串中匹配上,则这 \(len\) 块,明显都需要满足一个性质:每一块都能出现前 \(k\) 个字符至少一次。
因此,可以逆序预处理出字符串 \(s\) 所有下标位置不能匹配的最短子序列长度。
参考代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define eb(x) emplace_back(x)
using namespace std;
constexpr int N = 1e6 + 7;
int T = 1, n, m, w, cnt;
string s, t;
vector<int> v[26], mark(26);
int a[N];// 预处理维护答案
int main() {
IOS
cin >> n >> m >> s >> w;
for (int i = 0; i < n; ++ i) v[s[i] - 'a'].eb(i);// 用桶维护每个字母的下标序列
int res = 1;// 匹配不上的最短长度
a[n] = 1;// 匹配完子序列,字符串 s 无剩余字符,只需要添加一个字符即可使得子序列不再匹配
for (int i = n - 1; i >= 0; -- i) {
int idx = s[i] - 'a';
if (mark[idx] < res) {
++ mark[idx];
if (++ cnt == m) {
cnt = 0;
++ res;
}
}
a[i] = res;
}
while (w --) {
cin >> t;
int len = t.size(), ans = 0;
if (len <= n) {
int j = -1;
for (int i = 0; i < len; ++ i) {
int loc = t[i] - 'a';
auto it = upper_bound(v[loc].begin(), v[loc].end(), j);// 二分匹配出字符串 s 剩余子串第一个字符 t[i] 的位置
if (it == v[loc].end()) {
j = n;// 匹配的位置已经越界,即无法在字符串 s 中剩余子串匹配上
break;
}
j = *it;// 匹配上合理的位置
}
ans = a[j + 1];
}
cout << ans << '\n';
}
return 0;
}
浙公网安备 33010602011771号