模板
string s;
int fa[maxn],ch[maxn][26];
int len[maxn],last=1,tot=1;
struct SAM{
void Insert(int c)
{
int p=last,cur=++tot;
len[cur]=len[last]+1;
last=cur;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;//跳fail
if(!p) fa[cur]=1;//情况1
else{
int q=ch[p][c];
if(len[p]+1==len[q]) fa[cur]=q;//情况2
else{
//情况3
int clone=++tot;
len[clone]=len[p]+1;
for(int i=0;i<26;i++) ch[clone][i]=ch[q][i];
fa[clone]=fa[q];
fa[q]=fa[cur]=clone;
for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
}
};
此图为aababa的后缀自动机,包含三种情况。做题时可以直接用这个例子。
下面所有题型都以这个字符串举例
常见后缀自动机题型分类
常见用法:
1、判断是否为子串:
bool Search(string s)
{
int p=1;
int num=0;
int c=s[num]-'a';
int cnt=0;
while(ch[p][c]){
p=ch[p][c];
cnt++;
c=s[++num]-'a';
}
if(cnt==s.length()) return true;
else return false;
}
2、求不同字串个数
void cal()
{
for(int i=1;i<=tot;i++){
sum+=len[i]-len[fa[i]];
}
}
3、求所有子串长度之和
等差数列求和
void cal()
{
for(int i=1;i<=tot;i++){
int n=len[i]-len[fa[i]];
ans+=n*(len[fa[i]]+1)+n*(n-1)/2;
}
}
4、字典序第k大子串
本质来说就是回文自动机有多条路径,然后去选择第k大的路径
(1)、如果位置不同算作多个
先去求每个节点中的子字符串在总字符串中出现的次数,然后从最长串开始,依次去累加经过这个节点的字符串的出现次数。例如9节点,包含的字符串在总字符串中出现2次,因为他连着6号节点,也就相当于把跟着6号节点接下来的所有路径全部包含,所以只需要累加6号节点的sum值即可。
void Insert() siz[cur]=1;
void cal()
{
//siz数组为在字符串中出现的次数,sum为经过这个节点字符串的总数
for(int i=1;i<=tot;i++) c[len[i]]++;
for(int i=1;i<=tot;i++) c[i]+=c[i-1];
for(int i=1;i<=tot;i++) x[c[len[i]]--]=i;
for(int i=tot;i>=1;i--) siz[fa[x[i]]]+=siz[x[i]];
for(int i=1;i<=tot;i++){
sum[i]=siz[i];
}
siz[1]=sum[1]=0;
for(int i=tot;i>=1;i--){
for(int j=0;j<26;j++){
if(ch[x[i]][j]) sum[x[i]]+=sum[ch[x[i]][j]];
}
}
}
void dfs(int x,int k)
{
if(k<=siz[x]) return;
k-=siz[x];
for(int i=0;i<26;i++){
int y=ch[x][i];
if(!y) continue;
if(k>sum[y]){
k-=sum[y];
continue;
}