如何用 KMP 通过洛谷 P5410
我们需要求字符串 \(S\) 的 Z 函数。假设其长度为 \(n\)。
不妨设 \(\operatorname{lcp}(S,S[i,n])=len\),那么 \(len\) 为 \(S[1,i+len-1]\) 的一个 border。
可以对于一个前缀 \(S[1,i]\),动态维护其所有 border。如果一个 \(len\) 是 \(i\) 的 border 但不是 \(i+1\) 的 border,那么就将 \(z_{i-len+1}\) 更新。
当前缀从 \(i\) 移动到 \(i+1\) 时,会删掉若干个 border,其余 border 长度 \(+1\),并有可能加入长为 \(1\) 的 border。因此我们只需要支持快速删除所有不合法的 border。
考虑维护一个 \(jp_x\),表示失配树上 \(x\) 的最深的祖先 \(y\),满足 \(s_{x+1}\neq s_{y+1}\)。
现在我们考虑 \(x\) 是否合法,如果 \(s_{x+1}\neq s_{i+1}\) 则删除 \(x\),然后 \(x\leftarrow nxt_x\);否则 \(x\leftarrow jp_x\)。这样跳两次就能删一个 border,因此复杂度线性。
现在要考虑对每个 \(T\) 的后缀,求出其与 \(S\) 的 \(\operatorname{lcp}\)。
观察 kmp 的过程,实际上我们对于 \(T\) 的每个前缀 \(i\),都找出了最大的 \(x\),满足 \(S[1,x]=T[i-x+1,i]\)。其所有 border 即为所有满足条件的 \(x\)。
每次依然去暴力删除所有不合法的 \(x\) 来更新 \(p\),复杂度同样是线性。
int main() {
file();
ios::sync_with_stdio(0), cin.tie(0);
cin >> t >> s;
m = t.size(), t = " " + t + " ";
n = s.size(), s = " " + s + "@";
z[1] = n;
for(int i = 2, x = 0; i <= n; i++) {
for(; x && (s[x + 1] != s[i]); ) {
x = nxt[x];
}
if(s[x + 1] == s[i]) x++;
nxt[i] = x;
if(s[x + 1] == s[i + 1]) {
jp[i] = jp[x];
}else {
jp[i] = x;
}
for(int j = x; j; ) {
if(s[j + 1] == s[i + 1]) {
j = jp[j];
}else {
z[i - j + 1] = max(z[i - j + 1], j);
j = nxt[j];
}
}
}
for(int i = 1, x = 0; i <= m; i++) {
for(; x && (s[x + 1] != t[i]); ) {
x = nxt[x];
}
if(s[x + 1] == t[i]) x++;
for(int j = x; j; ) {
if(s[j + 1] == t[i + 1]) {
j = jp[j];
}else {
p[i - j + 1] = max(p[i - j + 1], j);
j = nxt[j];
}
}
}
ull ansz = 0, ansp = 0;
for(int i = 1; i <= n; i++) {
ansz ^= (ull)i * (z[i] + 1);
}
cout << ansz << "\n";
for(int i = 1; i <= m; i++) {
ansp ^= (ull)i * (p[i] + 1);
}
cout << ansp << "\n";
return 0;
}
浙公网安备 33010602011771号