题意:
给若干字符串,求它们的最长公共子串的长度。
题解:后缀自动机。
对第一个串建立SAM,并拓扑排序。
用后面的串分别匹配。
对于SAM,每个节点新增两个值ml,ans;
ml代表该节点满足单一字符串时的最大值,匹配完一个字符串后重置为0;
ans代表该节点满足所有字符串的最大值,初始化为该状态建立时的代表的子串的长度的最大值(max),每次匹配后更新为min(ans,ml)。
注意每次匹配完字符串,按照拓扑排序从后往前更新(保证父节点在子节点后被更新)用子节点的ml更新父节点的ml,证明如下:
根据后缀自动机上min[i]=max[fa[i]]+1,子节点能匹配的长度ml比父节点的max更长,但父节点是子节点的后缀,父节点也能匹配子节点匹配的子串的后max位,但实际在自动机上该匹配子串并没有转移到父节点过,其ml=0,或其ml比此次能匹配的max小,所以一旦子节点的ml不为0,父节点的ml定可修改为父节点的max值。
最后求所有节点ans的最大值即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=200005;
int ans;
char s[N];
struct sam
{
int cnt,last;
int to[N][26],fa[N],mx[N],ml[N],ans[N];
int c[N],q[N];
sam()
{
last=++cnt;
}
void extend(int c)
{
int p=last,np=last=++cnt;
mx[np]=mx[p]+1;ans[np]=mx[np];
while(p&&!to[p][c])to[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else
{
int q=to[p][c];
if(mx[q]==mx[p]+1)fa[np]=q;
else
{
int nq=++cnt;
mx[nq]=mx[p]+1;ans[nq]=mx[nq];
memcpy(to[nq],to[q],sizeof(to[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(to[p][c]==q)to[p][c]=nq,p=fa[p];
}
}
}
void build()
{
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++)
extend(s[i]-'a');
for(int i=1;i<=cnt;i++)c[mx[i]]++;
for(int i=1;i<=n;i++)c[i]+=c[i-1];
for(int i=cnt;i>=1;i--)q[c[mx[i]]--]=i;
}
void update()
{
int n=strlen(s+1);
int nowlen=0,p=1;
for(int i=1;i<=n;i++)
{
int c=s[i]-'a';
if(to[p][c])
nowlen++,p=to[p][c];
else
{
while(p&&!to[p][c])p=fa[p];
if(p)nowlen=mx[p]+1,p=to[p][c];
else nowlen=0,p=1;
}
ml[p]=max(ml[p],nowlen);
}
for(int i=cnt;i>=1;i--)
{
int p=q[i];
ans[p]=min(ans[p],ml[p]);
if(fa[p]&&ml[p])ml[fa[p]]=mx[fa[p]];
ml[p]=0;
}
}
}sam;
int main()
{
//freopen("lx.in","r",stdin);
sam.build();
while(scanf("%s",s+1)!=EOF)
sam.update();
for(int i=1;i<=sam.cnt;i++)
ans=max(ans,sam.ans[i]);
cout<<ans;
return 0;
}