题面
题意
给出最多十个字符串,求它们的最长公共子串.
做法
大致思路是对第一个字符串建出后缀自动机后,每个字符串都去匹配,对于自动机上每一个节点都用一个临时数组求出它所能匹配到的最长后缀长度,每个字符串匹配完后临时数组与上一次匹配后的结束状态取一个最小值.
一开始TLE了一次,WA了一次.
TLE那次我更新临时数组上的某个点时,沿着它的父节点向上更新了所有点,这样对于极限数据将会退化成O(n^2),因此会T.
后来发现我可以先对每一个点求出当前点的最长匹配后缀的长度,最后再沿着它的父节点节点向上更新.
但要注意后缀自动机上节点的父节点的标号不一定要比它小因为nq节点是q点的父节点,所以要先求出根据父节点值排的拓扑序,再一次更新父亲节点,这也正是我WA的原因.
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100100
using namespace std;
int last,tt,tmp[N<<1],ans,dfn[N<<1],dd;
char str[N];
bool vis[N<<1];
struct Node
{
int son[26],fail,len,pp;
}node[N<<1];
inline void add(int u)
{
int np=++tt,p,q,nq;
for(p=last;p!=-1&&!node[p].son[u];p=node[p].fail) node[p].son[u]=np;
node[np].len=node[last].len+1;
node[np].pp=node[np].len;
last=np;
if(p==-1)
{
node[np].fail=0;
return;
}
q=node[p].son[u];
if(node[q].len==node[p].len+1)
{
node[np].fail=q;
return;
}
nq=++tt;
node[nq]=node[q];
node[nq].len=node[p].len+1;
node[nq].pp=node[nq].len;
node[q].fail=node[np].fail=nq;
for(;p!=-1&&node[p].son[u]==q;p=node[p].fail) node[p].son[u]=nq;
}
inline void find()
{
int i,t,now=0,l=0,u,k;
for(i=1,t=strlen(str+1);i<=t;i++)
{
u=str[i]-'a';
for(;now&&!node[now].son[u];now=node[now].fail,l=node[now].len);
if(node[now].son[u])
{
l++;
now=node[now].son[u];
tmp[now]=max(tmp[now],l);
continue;
}
}
}
void in(int u)
{
if(vis[u]) return;
in(node[u].fail);
vis[u]=1;
dfn[++dd]=u;
}
int main()
{
int i,j,t;
scanf("%s",str+1);
node[0].fail=-1;
for(i=1,t=strlen(str+1);i<=t;i++)
{
add(str[i]-'a');
}
vis[0]=1;
for(i=1;i<=tt;i++)
{
in(i);
}
while(~scanf("%s",str+1))
{
memset(tmp,0,sizeof(tmp));
find();
for(j=tt;j>=1;j--)
{
i=dfn[j];
tmp[node[i].fail]=max(tmp[node[i].fail],tmp[i]);
node[i].pp=min(node[i].pp,tmp[i]);
}
}
for(i=1;i<=tt;i++)
{
ans=max(ans,node[i].pp);
}
cout<<ans;
}