Codeforces 1721E Prefix Function Queries
题目大意
给一个字符串 \(s(|s|\leq 10^6)\),再给 \(q(q\leq 10^5)\) 询问,每次询问添加一个长度小于 \(10\) 的字符串到 \(s\) 后面,求新添加的位置的 \(border\),每次询问独立。
题解
首先一看这不是很好写嘛,新增加了位置 \(i\),求 \(fail[i]\) 时直接从 \(fail[i-1]\) 开始跳 \(fail\) 就行了,每次询问结束后再回滚新增位置的 \(fail\)。交了一发结果T了。观察得每次询问都这样跳 \(fail\) 均摊并不是线性的,比如 \(s\) 是一个由 \(n\) 个 \(a\) 组成的字符串 \(aaa\cdots aaa\),现在在末尾添加一个 \(b\),发现求 \(fail[n+1]\) 需要从 \(fail[n]\) 开始跳 \(n\) 次,如果每次都询问 \(b\),那直接卡到了 \(O(n^2)\)。但仔细想想这种情况也是可以共用的,我们可以把询问离线,按字典序从小到大对所有询问的字符串排序,按字典序去做询问,这样每个询问的字符串就可以继承和上一个字符串的最长公共前缀这一部分的 \(fail\),大大减少了跳 \(fail\) 的次数。看到别人有用可持久化KMP的,之后也学一下。
Code
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn = 1e6 + 100;
int fail[maxn], id[100010];
vector<int> ans[100010];
string str[100010];
char s[maxn], buf[20];
void get_fail(int st, int len, const string& s2) {
bool flag = true;
for (int i = max(2, st), pos = fail[i - 1];i <= len;++i) {
if (!s2.empty()) {
if (flag && s[i] == s2[i - st]) { pos = fail[i]; continue; }
else { s[i] = s2[i - st]; flag = false; }
}
while (pos && (pos == len || s[i] != s[pos + 1])) pos = fail[pos];
if (s[i] == s[pos + 1]) ++pos;
fail[i] = pos;
}
for (int i = len + 1;i <= len + 10;++i) s[i] = 0;
}
bool cmp(int x, int y) { return str[x] < str[y]; }
int main() {
scanf("%s", s + 1);
int len = strlen(s + 1);
get_fail(1, len, str[0]);
int q; scanf("%d", &q);
for (int i = 1;i <= q;++i) {
scanf("%s", buf + 1);
str[i] = string(buf + 1);
id[i] = i;
}
sort(id + 1, id + q + 1, cmp);
for (int i = 1;i <= q;++i) {
get_fail(len + 1, len + str[id[i]].size(), str[id[i]]);
for (int j = len + 1;j <= len + str[id[i]].size();++j)
ans[id[i]].push_back(fail[j]);
}
for (int i = 1;i <= q;++i) {
for (auto x : ans[i])
printf("%d ", x);
printf("\n");
}
return 0;
}