【洛谷P6139】【模板】广义后缀自动机(广义 SAM)
题目
题目链接:https://www.luogu.com.cn/problem/P6139
给定 \(n\) 个由小写字母组成的字符串 \(s_1,s_2\ldots s_n\),求本质不同的子串个数。(不包含空串)
\(\sum |S|\leq 10^6\)。
思路
广义 SAM 直接在 SAM 的基础上加上特判即可。具体的,每次插入完一个字符串后 \(last\) 要赋值回 \(1\),再次插入时可能会遇到之前已经插入过的串,这个时候特判一下不要新建节点即可。
时间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2000010;
int Q,n,a[N],b[N];
char s[N];
struct SAM
{
int tot,last,ch[N][26],len[N],fa[N];
ll siz[N];
SAM() { tot=last=1; }
void ins(int c)
{
int p=last;
if (ch[p][c])
{
int q=ch[p][c];
if (len[q]==len[p]+1) last=q;
else
{
int nq=++tot;
len[nq]=len[p]+1; fa[nq]=fa[q]; last=nq;
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[q]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
else
{
int np=++tot;
len[np]=len[p]+1; last=np;
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
len[nq]=len[p]+1; fa[nq]=fa[q];
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
}
ll dfs(int x)
{
if (siz[x]) return siz[x];
siz[x]=(x>1);
for (int i=0;i<26;i++)
if (ch[x][i]) siz[x]+=dfs(ch[x][i]);
return siz[x];
}
}sam;
int main()
{
scanf("%d",&Q);
while (Q--)
{
scanf("%s",s+1);
n=strlen(s+1);
sam.last=1;
for (int i=1;i<=n;i++)
sam.ins(s[i]-'a');
}
printf("%lld",sam.dfs(1));
return 0;
}