【洛谷P3804】【模板】后缀自动机 (SAM)

题目

题目链接:https://www.luogu.com.cn/problem/P3804
给定一个只包含小写字母的字符串(S),
请你求出 (S) 的所有出现次数不为 (1) 的子串的出现次数乘上该子串长度的最大值。
(|S|leq 10^6)

思路

推荐 BlogOIwiki
SAM 可以在线性复杂度内解决一些关于子串的问题。例如经典的本质不同子串个数。
我们定义 (mathrm{endpos}(t)) 表示子串 (t) 在原串 (s) 中出现位置集合。具体的,

[mathrm{endpos}(t)={x|s_{x-|t|+1}cdots s_x=t} ]

对于 (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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/14252912.html