【洛谷P3804】【模板】后缀自动机 (SAM)
题目
题目链接:https://www.luogu.com.cn/problem/P3804
给定一个只包含小写字母的字符串\(S\),
请你求出 \(S\) 的所有出现次数不为 \(1\) 的子串的出现次数乘上该子串长度的最大值。
\(|S|\leq 10^6\)。
思路
推荐 Blog 和 OIwiki。
SAM 可以在线性复杂度内解决一些关于子串的问题。例如经典的本质不同子串个数。
我们定义 \(\mathrm{endpos}(t)\) 表示子串 \(t\) 在原串 \(s\) 中出现位置集合。具体的,
对于 \(\mathrm{endpos}(t)\) 相同的所有子串 \(t\),我们把他们归到一个等价类中,然后对于每一个等价类建立一个节点。
如果 \(\mathrm{endpos}(t)⫋\mathrm{endpos}(t')\),且不存在任意 \(t''(|t''|>|t'|)\) 满足 \(\mathrm{endpos}(t)⫋\mathrm{endpos}(t'')\),那么我们就从 \(t'\) 所在类的节点向 \(t\) 所在类的节点连一条边。
容易发现,连边结束后,所有的点构成了一棵树,我们称其为 parent 树。可以证明树上的点数是 \(O(n)\) 的。
我们记 \(\mathrm{len}(x)\) 表示节点 \(x\) 所在的等价类中,长度最长的子串的长度,\(\mathrm{minlen}(x)\) 表示最短长度。那么容易发现 \(\mathrm{minlen}(x)=\mathrm{len}(fa_x)+1\)。因为显然在原串中它们是包含关系且只差一个字符。
一个后缀自动机(SAM)的节点和 parent 树完全一致,但是连边方式不同。在 SAM 中,一个类所对应的节点会向另一个类连一条有向边,当且仅当在这个类的任意子串的末尾添加一个字符 \(c\),得到的串的 \(\mathrm{endpos}\) 集合等于后者所在的类的 \(\mathrm{endpos}\) 集合。那么这条有向边的权值就为这个字符 \(c\)。
依旧是可以证明,SAM 的边数是 \(O(n)\) 的。具体可以看上面推荐的文章。wtcl。
然后利用 parent 树和后缀自动机,就可以解决字符串的很多问题。但是为了保证复杂度,如果需要排序,那么要采用基数排序。
回到本题,建出 parent 树后,容易发现一个点所表示的等价类中,所有子串出现的次数都等于 parent 树上这个节点的子树大小。
那么直接求出每一个节点的大小,乘上 \(\mathrm{len_x}\) 取最大值即可。
时间复杂度 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2000010;
int n,a[N],cnt[N];
ll ans,f[N];
char s[N];
struct SAM
{
int tot,last,ch[N][26],fa[N],len[N];
SAM() { tot=last=1; }
void ins(int c)
{
int np=++tot,p=last;
len[np]=len[p]+1; last=np; f[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;
}
}
}
void topsort()
{
for (int i=1;i<=tot;i++) cnt[len[i]]++;
for (int i=1;i<=tot;i++) cnt[i]+=cnt[i-1];
for (int i=tot;i>=1;i--) a[cnt[len[i]]--]=i;
for (int i=tot;i>=1;i--)
{
if (f[a[i]]>1) ans=max(ans,f[a[i]]*len[a[i]]);
f[fa[a[i]]]+=f[a[i]];
}
}
}sam;
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for (int i=1;i<=n;i++)
sam.ins(s[i]-'a');
sam.topsort();
printf("%lld\n",ans);
return 0;
}