This is a SAM for string acadd
先谈我对后缀自动机的理解:
误解1:后缀自动机是识别字符串所有后缀的东西
这不完全正确,它可以识别所有子串,于是也可以识别所有后缀
误解2:这是一个nlogn的算法。
错误后缀自动机极限时间复杂度是O(n*2-1)比SA和S tree优
然后是后缀自动机可以干什么:
可以求LCS
什么?你说把他转成LIS跑nlogn?
这里的LCS指Longest Common Substring
既最长公共子串。
所以不是LCS最长公共子序列。
然后建出了SAM就好了
建的过程不难
几个定义
有一个小问题:
1 首先建立储存当前字符x的结点np,找到之前最后一个建立的结点(因为它一定满足性质②),然后就不断按pre指针跳(直到跳到有x儿子的结点为止).
2 在这个过程当中我们会便利到没有x儿子的节点,没有x儿子的节点很好处理,直接将这个点连接过去即可
3 情况1:step[p]+1=step[q]。这意味着我们p的下一个节点就是q,在状态p和状态q中没有多余状态存在,所以如果p对于当前串可以接受后缀,那么q对于新串也可以接受后缀,所以pre[np]=q即可。
4 step[p]<step[q]+1。新建一个nq节点。这时,只要把q的son边和pre边都copy到nq上,然后把nq的pre改为p,再把q和np的pre都改为nq.
两个重要性质
1.right集合。也就是我们所说的:step。
这个是什么?这表示符合这种字符串的集合。
2.Parent 树
也就是Pre所构成的树。
我们利用Parent树对siz合并
已达到求每个子串个数的目的。
通常并不需要真正建树,只需要用桶排个序就好了。
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
char s[N];
int c[N]={};//一个桶,计数排序
int a[N]={};//
int ans;
struct Suffix_AuTo{
struct Node{
int vis[26];
int step;
int pre;
int siz;
}SA[N];
int last,cnt;
void Insert(char C){
// cout<<last<<'\n';
int p=last;
int np=++cnt;
last=np;
SA[np].step=SA[p].step+1;
for(;p&&!SA[p].vis[C-'a'];p=SA[p].pre)SA[p].vis[C-'a']=np;
if(!p)SA[np].pre=1;
else{
int q=SA[p].vis[C-'a'];
if(SA[q].step==SA[p].step+1){
SA[np].pre=q;
}
else{
int nq=++cnt;
SA[nq].step=SA[p].step+1;
memcpy(SA[nq].vis,SA[q].vis,sizeof(SA[q].vis));
SA[nq].pre=SA[q].pre;
SA[q].pre=SA[np].pre=nq;
for(;SA[p].vis[C-'a']==q;p=SA[p].pre)SA[p].vis[C-'a']=nq;
}
}
// cout<<SA[np].step<<" np "<<'\n';
SA[np].siz=1;
}
void Build(){
scanf("%s",&s);
int len=strlen(s);
last=cnt=1;
for(int i=0;i<len;i++)Insert(s[i]);
}
void Calc(){
for(int i=1;i<=cnt;i++)c[SA[i].step]++;
for(int i=1;i<=cnt;i++)c[i]+=c[i-1];
for(int i=1;i<=cnt;i++)a[c[SA[i].step]--]=i;
for(int i=cnt;i>=1;i--){
int p=a[i];
SA[SA[p].pre].siz+=SA[p].siz;
// cout<<SA[p].siz<<" "<<SA[p].step<<'\n';
if(SA[p].siz>1)ans=max(ans,SA[p].siz*SA[p].step);
}
cout<<ans;
}
}SAM;
int main(){
// freopen("P3804.in","r",stdin);
SAM.Build();
SAM.Calc();
}