CF2104E Unpleasant Strings

给定一个长度为 \(n\) 的字符串 \(s\),进行 \(q\) 次询问。每次询问一个字符串 \(t\),问在 \(t\) 后面 最少 添加多少个字符,能使得 \(t\) 不再是 \(s\) 的一个子序列。数据范围 \(n\le 10^6, \sum |t_i|\le 1e6, q\le 2\times 10^5\)

除此之外,给定 \(k\le 26\),题目中所有字符串(包括你添加的字符)只能使用前 \(k\) 个英文字母。这个要求对解题没有影响。

我们先在 \(s\) 中找到 \(t\)\(t\) 可能作为子序列在 \(s\) 中出现了很多次,我们只考虑最前面的一次。比如 \(s=abcabc,t=ac\)\(t\)\(s\) 中出现了三次,但我们只考虑 \(\textcolor{red}{a}b\textcolor{red}{c}abc\)。这是因为,先出现的子序列所需要添加的字符个数一定大于等于后出现的子序列(所需的字符个数)。

直接匹配的复杂度是 \(O(nq)\) 的,所以我们可以预处理一个 \(next[i][j]\) 数组,表示从每个位置 \(i\) 后面的第一个字符 \(j\) 的位置,然后匹配的时候直接跳(因为 \(\sum |t_i|\le 10^6\))。

找到子序列(后面提到子序列都是说第一个)的终止位置 \(p\),我们做一个 dp。\(d_i\) 表示以 \(i\) 结尾的子序列,最少要添加多少个字符。从后往前更新,\(dp[i]=\min\{dp[next[i][j]]\}+1\),表示如果添加一个 \(j\) 字符转移到 \(dp[next[i][j]]\),还需要再添加多少个字符。这个 dp 可以 \(O(nk)\) 预处理。

下面是 base-1 的 AC 代码:

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

int main() {
    int n, k;
    string s;
    cin >> n >> k >> s;
    s = " " + s;
    // 构造 next[]
    vector next(n + 1, vector<int>(k));
    ranges::fill(next[n], n + 1);
    for (int i = n - 1; ~i; i--) {
        next[i] = next[i + 1];
        next[i][s[i + 1] - 'a'] = i + 1;
    }
    // 构造 dp 数组
    vector<int> dp(n + 2, n);
    dp[n + 1] = 0;
    dp[n] = 1;
    for (int i = n - 1; ~i; i--) {
        for (int j = 0; j < k; j++) {
            dp[i] = min(dp[i], dp[next[i][j]] + 1);
        }
    }
    // 查询
    int q;
    cin >> q;
    while (q--) {
        string t;
        cin >> t;
        int m = t.size();
        t = " " + t;
        int cur = 0;
        for (int i = 1; i <= m; i++) {
            if (cur > n) break;
            cur = next[cur][t[i] - 'a'];
        }
        cout << dp[cur] << '\n';
    }
    return 0;
}
posted @ 2025-04-29 11:52  XYukari  阅读(88)  评论(0)    收藏  举报