【模板】扩展 KMP(Z 函数)
题目链接:luogu P5410
题目大意
给你一个字符串,要你求它每个后缀跟它 LCP 的长度。(Z 函数)
给你两个字符串,要你求一个字符串每个后缀跟另一个字符串 LCP 的长度。(exKMP)
思路
这个东西感觉有点马拉车的感觉。
考虑知道了当前的答案
z
1
∼
i
−
1
z_{1\sim i-1}
z1∼i−1,考虑怎么求
z
i
z_i
zi。
首先我们肯定是找到一个可以匹配的串
[
l
,
r
]
[l,r]
[l,r],那肯定是找
r
r
r 最大的,记录当前是
l
,
r
l,r
l,r。
然后如果
i
⩽
r
i\leqslant r
i⩽r,我们考虑利用它,通过证明可以得到
r
i
⩾
min
(
r
−
i
+
1
,
r
i
−
l
+
1
)
r_i\geqslant \min(r-i+1,r_{i-l+1})
ri⩾min(r−i+1,ri−l+1)
证明考虑逐位证,设
1
⩽
x
⩽
min
(
r
−
i
+
1
,
r
i
−
l
+
1
)
1\leqslant x\leqslant \min(r-i+1,r_{i-l+1})
1⩽x⩽min(r−i+1,ri−l+1)
s
i
+
x
−
1
=
s
l
+
(
i
+
x
−
l
)
−
1
s_{i+x-1}=s_{l+(i+x-l)-1}
si+x−1=sl+(i+x−l)−1
=
s
1
+
(
i
+
x
−
l
)
−
1
=
s
i
+
x
−
l
=s_{1+(i+x-l)-1}=s_{i+x-l}
=s1+(i+x−l)−1=si+x−l(左边的限制条件)
=
s
1
+
(
i
−
l
+
1
)
+
x
=s_{1+(i-l+1)+x}
=s1+(i−l+1)+x
=
s
1
+
x
=s_{1+x}
=s1+x(当
x
≤
z
i
−
l
+
1
x\leq z_{i-l+1}
x≤zi−l+1)
然后我们再暴力扩展,然后维护最大
r
r
r 对于的
l
,
r
l,r
l,r。
然后每个数只会别暴力扩展一次,所以复杂度是
O
(
n
)
O(n)
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;
}