1、 两个字符串 s 和 t(假如 s的长度大于t),先构造s 的后缀自动机。
后缀自动机一个特点, 从根节点开始跑, 到达任何一个点,对应
s 的一个子串。
2、 对于t的每一个前缀 t[1~i], 都在 s 中寻找这个前缀 t[1~i] 的最长后缀。
方法是在s 的后缀自动机跑,用v 表示当前跑到的节点, lcs 表示当前匹配的长度。
现在添加一个字符 t[i] 并为其重新计算答案:
如果存在一个从v 到字符 t[i] 的转移,我们只需要转移并让 lcs++
如果不存在,v = fa[v], v点沿着后缀链接边回溯, 同时 lcs = len[v]
具体看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
char s[N], t[N], tmp[N];
int n;
ll cnt[N], ans;
int tot = 1, np = 1;
int fa[N], ch[N][26], len[N];
void extend(int c)
{
// p回跳指针,np 新节点
// q链接点,nq 新链接点
int p = np; np = ++tot; //p指向旧节点, np是新点
len[np] = len[p] + 1; cnt[np] = 1; //子串出现次数
//p沿着链接边回跳,从旧节点向新点建转移边
for(; p && !ch[p][c]; p = fa[p])
ch[p][c] = np;
//1、 如果c是新字符, 从新点向根节点建链接边
if(p == 0)
fa[np] = 1;
else{
int q = ch[p][c]; //q是链接点
//2、 若链接点合法,从新点向链接点接边
if(len[q] == len[p] + 1)
fa[np] = q;
//3、若链接边不合法, 则裂开q点,重建两类边
else{
int nq = ++tot; //nq 是新建链接点
len[nq] = len[p] + 1;
//重建 nq, q, np 的链接边
fa[nq] = fa[q]; fa[q] = nq; fa[np] = nq;
// 指向q的转移边改为指向nq
for(; p && ch[p][c] == q; p = fa[p])
ch[p][c] = nq;
//从q发出的转移边复制给 nq
memcpy(ch[nq], ch[q], sizeof(ch[q]));
}
}
}
void solve()
{
ll lcs = 0;
int size = strlen(t + 1), v = 1;
for(int i = 1; i <= size; ++i)
{
int u = t[i] - 'a'; //遍历t的每一个字符 t[i]
if(ch[v][u]) //匹配上了, lcs++, 同时更新 ans
{
lcs++;
ans = max(ans, lcs);
v = ch[v][u];
}else{
while(fa[v]) // 如果后缀自动机当前点v 没有匹配上, v点就沿着后缀链接边回退
{
v = fa[v];
lcs = len[v]; // 同时更新lcs, 等于v点 的 fa[v] 的 len值
if(ch[v][u]) // 回退过程中,能匹配上, lcs++, 同时更新 ans。 然后退出循环
{
lcs++;
ans = max(ans, lcs);
v = ch[v][u];
break;
}
}
}
}
}
int main()
{
scanf("%s", s + 1);
scanf("%s", t + 1);
if(strlen(s + 1) < strlen(t + 1))
{
strcpy(tmp + 1, s + 1);
strcpy(s + 1, t + 1);
strcpy(t + 1, tmp + 1);
}
n = strlen(s + 1);
for(int i = 1; i <= n; ++i)
{
extend(s[i] - 'a');
}
solve();
printf("%lld\n", ans);
return 0;
}