【luogu P5410】【模板】扩展 KMP(Z 函数)(字符串)
【模板】扩展 KMP(Z 函数)
题目链接:luogu P5410
题目大意
给你一个字符串,要你求它每个后缀跟它 LCP 的长度。(Z 函数)
给你两个字符串,要你求一个字符串每个后缀跟另一个字符串 LCP 的长度。(exKMP)
思路
这个东西感觉有点马拉车的感觉。
考虑知道了当前的答案 \(z_{1\sim i-1}\),考虑怎么求 \(z_i\)。
首先我们肯定是找到一个可以匹配的串 \([l,r]\),那肯定是找 \(r\) 最大的,记录当前是 \(l,r\)。
然后如果 \(i\leqslant r\),我们考虑利用它,通过证明可以得到 \(r_i\geqslant \min(r-i+1,r_{i-l+1})\)
证明考虑逐位证,设 \(1\leqslant x\leqslant \min(r-i+1,r_{i-l+1})\)
\(s_{i+x-1}=s_{l+(i+x-l)-1}\)
\(=s_{1+(i+x-l)-1}=s_{i+x-l}\)(左边的限制条件)
\(=s_{1+(i-l+1)+x}\)
\(=s_{1+x}\)(当 \(x\leq z_{i-l+1}\))
然后我们再暴力扩展,然后维护最大 \(r\) 对于的 \(l,r\)。
然后每个数只会别暴力扩展一次,所以复杂度是 \(O(n)\)。
至于两个字符之间的 exKMP,我们就一样的匹配法。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N = 2e7 + 5;
char a[N], b[N];
int an, bn, z[N], p[N];
ll ans;
void get_Z(char *s, int n) {
for (int i = 1; i <= n; i++) z[i] = 0;
z[1] = n;
for (int i = 2, l = 0, r = 0; i <= n; i++) {
if (i <= r) z[i] = min(z[i - l + 1], r - i + 1);
while (i + z[i] <= n && s[i + z[i]] == s[1 + z[i]]) z[i]++;
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
}
void exKMP(char *a, int n, char *b, int m) {
for (int i = 1; i <= n; i++) p[i] = 0;
for (int i = 1, l = 0, r = 0; i <= n; i++) {
if (i <= r) p[i] = min(z[i - l + 1], r - i + 1);
while (i + p[i] <= n && a[i + p[i]] == b[1 + p[i]]) p[i]++;
if (i + p[i] - 1 > r) l = i, r = i + p[i] - 1;
}
}
int main() {
scanf("%s", a + 1); scanf("%s", b + 1);
an = strlen(a + 1); bn = strlen(b + 1);
get_Z(b, bn);
ans = 0; for (int i = 1; i <= bn; i++) ans ^= 1ll * i * (z[i] + 1); printf("%lld\n", ans);
exKMP(a, an, b, bn);
ans = 0; for (int i = 1; i <= an; i++) ans ^= 1ll * i * (p[i] + 1); printf("%lld", ans);
return 0;
}