首先构造sam还是比较巧的
首先它的总点数和边数不会超过2*n,而且每个节点下面相同的字母节点只有一个
所以它实质上是把一个字母用了多次
sam是每次加入一个节点,然后合理的安排位置、边、辅助点使之更优
1、父指针:指向同样能接受下一个字符的节点:
设根节点(起点)为s
如 aca ,这时第二个a的父指针就指向第一个a,这样下一个节点x就会和这两个a连边,使s-a-c-a-x 和s-a-x 同时存在
如果不用父指针,都从s出发,那s后面就有两个字符为a的节点,显然不优(匹配a时不知道走哪个,就不是自动机了)
所以这第一个a就有两个意义了
父指针使自动机区别于暴力加后缀();
还有添加辅助节点,,如abacc 第二个c会找到第一个c来尝试优化,,但起点后面只能有一个c,并且这个c还肩负着后缀c、cc 那怎么办?
新建一个节点c,复制第一个c和根、子节点的关系,使它具有和第一个c相同的功能,于是,cc串就出现了
它还需要作为后缀c,,所以本身可以接受节点,第一个c和第二个c的父指针都指向它,这样它就相当于有两个意义
这个题要求的使子串长度*出现次数的最大值,,出现次数好说,因为父指针都是指向前面的相同字符的节点,所以倒序递推即可在第一次出现该字符时统计出字符出现次数
还剩一个长度,,l数组就有用了,它记录的是从根节点出发的最长子串、所以计数排序之后,如果它的节点的出现次数为n,那它前面的节点的出现次数也一定是>=n的(因为爬相同的节点需要一个一个爬,不可能跳步),所以size*l取最值即可
码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define N 1000006
int cnt,last;
int ch[N<<1][28],fu[N<<1];
ll sz[N<<1],l[N<<1],ans,c[N<<1],a[N<<1];
char str[N];
void jia(int o)
{
int np=++cnt,p=last;last=np;l[np]=l[p]+1;
for(;p&&ch[p][o]==0;p=fu[p])ch[p][o]=np;
if(!p)fu[np]=1;
else
{
int q=ch[p][o];
if(l[p]+1==l[q])fu[np]=q;
else
{
int i,nq=++cnt;
l[nq]=l[p]+1;
for(i=0;i<=26;i++)
ch[nq][i]=ch[q][i];
fu[nq]=fu[q];
fu[q]=fu[np]=nq;
for(;ch[p][o]==q;p=fu[p])ch[p][o]=nq;
}
}
sz[np]=1;
}
int main()
{
scanf("%s",str);
cnt=1;
last=1;
int i,n=strlen(str);
for(i=0;i<n;i++)jia(str[i]-'a');
for(i=1;i<=cnt;i++)c[l[i]]++;
for(i=1;i<=cnt;i++)c[i]+=c[i-1];
for(i=1;i<=cnt;i++)a[c[l[i]]--]=i;
//for(int i=1;i<=cnt;i++)cout<<a[i]<<" "<<l[a[i]]<<endl;
for(i=cnt;i>=1;i--)
{
int p=a[i];
sz[fu[p]]+=sz[p];
if(sz[p]>1)ans=max(ans,sz[p]*l[p]);
}
printf("%lld",ans);
}