扩展kmp是用来处理这样一类问题的:有一个文本串S,一个模式串T,求对于S串每一个后缀与T串的最长公共前缀,有学过后缀数组的同学可能会发现,完全可以把两串连接起来然后求lcp,复杂度为O(nlogn),但是这个算法的时间复杂度和kmp一样,都是O(n)的,要优于后缀数组做法(虽然一般是没什么差别的)。最终答案保存于extend[i]中,extend[i]表示S串从i开始的后缀与T串最长公共前缀长度。如果遍历extend数组,统计extend[i]等于strlen(T)的出现次数,那它就实现了kmp的功能,因此称其为扩展kmp算法。
以P5410 【模板】扩展 KMP(Z 函数) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)这道题为例,介绍一下模板。
在求extend数组之前,需要一个辅助数组_next,就像kmp算法一样。不过这里的_next数组含义与kmp中不同,_next[i]表示T串从i开始的后缀与T串最长公共前缀长度,比较一下之前extend数组含义,可以发现求_next数组的过程不过是自己和自己匹配了一遍,于是只需要写正确求extend数组那部分代码,就可以复制一遍求_next数组了,就像kmp算法中求_next一样。
核心思想其实是和manacher算法很接近的,都是记录一下当前已求出的最靠右的区间,根据某个数组得到当前位置已经匹配成功的长度,如果小于区间右端点就直接记录答案,如果大于右端点就暴力匹配。
代码细节可以参考这篇博客:扩展 KMP 算法 - 刘毅的个人博客 (ethsonliu.com)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
//以洛谷P5410为例
char S[20000010], T[20000010];
int n, m, _next[20000010], extend[20000010];
//求_next[]
void GetNext()
{
int l = 0, r = 0;
_next[0] = m;
for (int i = 1; i < m; i++)
{
if (i >= r || i + _next[i-l] >= r)
{
if (i >= r)
r = i;
while (r < m && T[r] == T[r-i])
r++;
_next[i] = r-i;
l = i;
}
else
_next[i] = _next[i-l];
}
}
//求extend[]
void GetExtend()
{
int l = 0, r = 0;
GetNext();
for (int i = 0; i < n; i++)
{
if (i >= r || i+_next[i-l]-1 >= r-1)// i>=p的作用:举个典型例子,S和T无一字符相同
{
if (i >= r)
r = i;
while (r < n && r-i < m && S[r] == T[r-i])//暴力匹配
r++;
extend[i] = r-i;
l = i;
}
else//根据next数组定义
extend[i] = _next[i-l];
}
}
signed main()
{
cin >> S >> T;
n = strlen(S), m = strlen(T);
GetExtend();
long long ans = _next[0]+1;
for(int i = 1; i < m; i++)
ans = ans^((i+1)*((long long)_next[i]+1));
printf("%lld\n", ans);
ans = extend[0]+1;
for(int i = 1; i < n; i++)
ans = ans^((i+1)*((long long)extend[i]+1));
printf("%lld\n", ans);
return 0;
}