后缀自动机的一些基本知识
参考资料和例子:http://hihocoder.com/problemset/problem/1441
简而言之,后缀自动机(SAM),是一个有限状态自动机(DFA)
SAM分为两个部分,一部分是一个Dag,另一部分是Parent树。
——laofu
...
后缀自动机的DAG部分
后缀的\(Dag\)(有向无环图)部分由状态和转移函数构成,
状态表示原字符串若干个匹配结束位置相同的子串,这个结束位置的集合称为\(endpos\)(又称\(right\))
我们把这些子串中最长的记录下来,称为\(len\)
转移函数指原状态所包含的子串可以继续往后添加使得自动机识别的字符
由SAM的性质,沿着SAM的转移所得到的字符串一定是原串的一个子串
因此SAM的DAG部分相当于把原串的所有字串所构成的trie树压缩了
后缀自动机的Parent树部分
后缀的Parent树部分由Suffix-Links组成
沿着Suffix-Links走,我们能走到固定前缀的所有后缀
因此Parent树就是原串反串的后缀树
利用这个性质我们可以求出两后缀的\(LCP\)等经典后缀数组问题
我们记录一个\(fa\)数组表示Parent树中每个节点的父亲是谁
可以知道这个\(fa\)指针可以作为trie树中的fail指针
因此我们可以在SAM中匹配一个串中是否存在给定串的子串
Parent树中每个状态的\(sz\)实际表示\(|endpos|\)
将每个状态按照\(len\)递增的顺序进行遍历,那么遍历状态的顺序实际dfs Parent树和DAG的顺序
因此我们基数排序后按照\(len\)递减的顺序遍历可以求出\(sz\)
for(RG int i=1;i<=tot;i++)t[len[i]]++;
for(RG int i=1;i<=tot;i++)t[i]+=t[i-1];
for(RG int i=1;i<=tot;i++)a[t[len[i]]--]=i;
for(RG int i=tot;i;i--)sz[fa[a[i]]]+=sz[a[i]];
如何构建SAM
推荐教程和例子:http://hihocoder.com/problemset/problem/1445中的解题方法提示
分三种情况讨论;
先背代码也可以
char s[N];
int n,lst=1,tot=1,tr[N][30],fa[N],len[N],sz[N],a[N],t[N];ll ans;
il void extend(int c){
RG int u=++tot,v=lst;lst=u;len[u]=len[v]+1;
while(v&&!tr[v][c])tr[v][c]=u,v=fa[v];
if(!v)fa[u]=1;
else{
RG int x=tr[v][c];
if(len[x]==len[v]+1)fa[u]=x;
else{
RG int y=++tot;len[y]=len[v]+1;
memcpy(tr[y],tr[x],sizeof(tr[y]));
fa[y]=fa[x];fa[u]=fa[x]=y;
while(v&&tr[v][c]==x)tr[v][c]=y,v=fa[v];
}
}
sz[u]=1;
}
int main()
{
scanf("%s",s+1);n=strlen(s+1);
for(RG int i=1;i<=n;i++)extend(s[i]-'a'+1);
for(RG int i=1;i<=tot;i++)t[len[i]]++;
for(RG int i=1;i<=tot;i++)t[i]+=t[i-1];
for(RG int i=1;i<=tot;i++)a[t[len[i]]--]=i;
for(RG int i=tot;i;i--)sz[fa[a[i]]]+=sz[a[i]];
for(RG int i=1;i<=tot;i++)if(sz[i]!=1)ans=max(ans,1ll*sz[i]*len[i]);
printf("%lld\n",ans);return 0;
}