考虑如下的问题:
- 给出一个长度
n
的字符串
S0..n−1 - 和一个长度
m
的字符串
T0..m−1 - 问
S
的哪个后缀和
T 具有最长的公共前缀(Longest Common Prefix,以下简称LCP)
让我们来简单分析一下,暴力做法就是枚举
S
的每一个后缀和
这个做法中,主要是重复运算拖慢了速度,效率不高。
首先我们可以试着和kmp一样的思路:
先假设
T
的每一个后缀
然后再学习manacher算法的思想:设答案伸得最远的后缀(不包括原串)为 Tp..n−1 ,如果其公共前缀没有包含字符 Ti ,就暴力求 nexti (当然, i=1 时也暴力求),否则可以像manacher一样直接拿现成结果。
如图所示(图示转自yhf4aspe的blog):
那么可以直接求出的部分就是 nexti−p 。
因为 Ti..p+nextp−1=T0..nextp−1 ,
求不出的部分就可以从 u 开始暴力求。
代码实现:
next[0] = m; for(i = 0; T[i] == T[i + 1]; i++); next[1] = i; p = 1; for(i = 2; i < m; i++) { u = p + next[p]; //u为最右端位置 if(i + next[i - p] < u) next[i] = next[i - p]; //如果答案可以直接取 else { //如果答案需要探索 for(j = max(u - i, 0); T[i + j] == T[j]; j++); next[i] = j; p = i; } }
至于
时间复杂度分析:
第一步求
第二步求ex时同理, Θ(n) 。
总的时间复杂度为 Θ(m+n) 。
扩展kmp算法的模板:
//扩展kmp by LKB 2016.8.7
#include <iostream>
#include <string>
using namespace std;
const int maxlen = 1e5;
string S, T;
int next[maxlen];
//next[i]储存T的每一个后缀T[i..n-1]和T[0..n-1]本身的最长公共前缀
int ex[maxlen];
int main() {
cin >> S >> T;
int n = S.size();
int m = T.size();
next[0] = m;
for(int i = 0; i + 1 < m; i++) //预处理计算next[1]
if(T[i] == T[i + 1]) ++next[1];
else break;
int p = 1; //答案伸得最远的后缀(不包括原串)为T[p..n-1]
for(int i = 2; i < m; i++) {
int u = p + next[p]; //u为最右端位置
if(i + next[i - p] < u) next[i] = next[i - p]; //如果答案可以直接取
else { //如果答案需要探索
int j;
for(j = max(u - i, 0); T[i + j] == T[j]; j++);
next[i] = j;
p = i;
}
}
for(int i = 0; i + 1 < n; i++)
if(S[i] == T[i]) ++ex[0];
else break;
p = 0;
for(int i = 1; i < n; i++) {
int u = p + ex[p]; //u为最右端位置
if(i + next[i - p] < u) ex[i] = next[i - p]; //如果答案可以直接取得
else { //如果答案需要探索
int j;
for(j = max(u - i, 0); S[i + j] == T[j]; j++);
ex[i] = j;
p = i;
}
}
int ans = 0;
for(int i = 0; i < n; i++) ans = max(ans, ex[i]);
cout << ans << endl;
return 0;
}