Z函数可以在\(O(n)\)时间内求出自己的所有后缀和本身的LCP(最长公共前缀),和任意字符串\(T\)和其的LCP。
原理就是考虑之前的lcp,可以通过之前的lcp这一位和前缀某一位一致,对应到前缀某一位的LCP,再向右扩展。注意到,右边界最多被扩展1次所以是线性复杂度。
struct Z {
int z[MAXN], p[MAXN];
void get_Z(char* s, int n) {
int t = 1, lim; z[1] = 1;
for (int i = 2; i <= n; ++i) {
lim = t + z[t] - 1; //[1,t-1],[t,lim]
if (i + z[i - t + 1] - 1 < lim) {
z[i] = z[i - t + 1];
} else {
z[i] = max(0, lim - i + 1);
while (s[i + z[i]] == s[z[i] + 1])z[i]++;
}
if (i + z[i] > t + z[t])t = i;
}
z[1] = n;
}
void exkmp(char* s, char* u, int n, int m) {
int t = 1, lim;
p[1] = 0;
for (int i = 1; i <= m; ++i) {
lim = t + p[t] - 1; //[1,t-1],[t,lim]
if (i + z[i - t + 1] - 1 < lim) {
p[i] = z[i - t + 1];
} else {
p[i] = max(0, lim - i + 1);
while (u[i + p[i]] == s[p[i] + 1] && p[i] + 1 <= n)p[i]++;
}
if (i + p[i] > t + p[t])t = i;
}
}
}p;
LCP
性质:
- 若字符串\(S\),的\(i\)后缀和\(j\)后缀的\(LCP \geq j-i+1 (i < j)\),那么长度为$ j+LCP-i $ 的子串是以\(S[i,j-1]\)为周期的串。
更进一步,若字符串\(i\)的\(z\)函数值\(i+z[i]-1 = n\),那么这个字符串就是以\(S[1,i-1]\)为周期的串。在后缀章节会学习如何求出任意两个位置的LCP。
- LCS (最长公共后缀)同理。把字符串翻转一遍,求出LCP即可。