题目链接
链接是洛谷有翻译的。
题意:
给你两个长度不超过250000的字符串,求两个串的最长公共子串。
题解:
看起来是个比较经典的问题。似乎SAM处理子串的能力很强啊。
做法是先对第一个串建出SAM,然后我们考虑parent树的含义,其实我觉得在某种意义下parent树可以理解为AC自动机的fail指针,因为它保证了子节点表示的字符串集合是父节点的一个真子集,所以如果在子节点匹配不上的话,下一个应该尝试是否能在parent树上这个点的父节点匹配,所以在这里可以当作fail指针使用。那么这道题就比较简单了,我们只需要枚举第二个串的每一个字符,然后看之前跳到了哪个位置,如果有当前字符这个出边,那么就走过去,否则就顺着parent树往上跳,直到有这样的出边或者到根为止。对于每一个在第二个串中的字符,最后停留在的位置的字符串长度就是在第二个串到当前字符为止的一个最长的能与第一个串匹配的一个后缀的长度,每次用这个长度更新答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,ch[500010][27],lst=1,fa[500010],cnt=1,rt=1,len[500010],ans;
char s[250010],s1[250010];
inline void insert(int x)
{
int cur=++cnt,pre=lst;
lst=cur;
len[cur]=len[pre]+1;
for(;pre&&!ch[pre][x];pre=fa[pre])
ch[pre][x]=cur;
if(!pre)
fa[cur]=rt;
else
{
int ji=ch[pre][x];
if(len[ji]==len[pre]+1)
fa[cur]=ji;
else
{
int gg=++cnt;
len[gg]=len[pre]+1;
memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
fa[gg]=fa[ji];
fa[ji]=fa[cur]=gg;
for(;pre&&ch[pre][x]==ji;pre=fa[pre])
ch[pre][x]=gg;
}
}
}
int main()
{
scanf("%s",s+1);
scanf("%s",s1+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)
insert(s[i]-'a');
n=strlen(s1+1);
int cur=rt,l=0;
for(int i=1;i<=n;++i)
{
int x=s1[i]-'a';
if(ch[cur][x])
{
l++;
cur=ch[cur][x];
ans=max(ans,l);
continue;
}
for(;cur&&!ch[cur][x];cur=fa[cur]);
if(cur)
{
l=len[cur]+1;
cur=ch[cur][x];
}
else
{
cur=1;
l=0;
}
ans=max(l,ans);
}
printf("%d\n",ans);
return 0;
}