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

本文提供了一个后缀自动机(SAM)的C++模板代码,详细解析了如何使用SAM解决特定问题:求字符串所有子串出现次数不为1时,其出现次数乘以长度的最大值。通过构建SAM并利用DFS统计子树大小,巧妙地解决了问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板题
本文只是放上后缀自动机的模板,并不会对后缀自动机的原理进行讲解。

首先放上模板:

#include <bits/stdc++.h>
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 2e6+7;
struct node
{
	int ch[26];
	int len,fa;
}point[maxn<<1];
int las = 1,tot = 1,ans;
inline void add(char c)
{
	int p = las;
	int np = las = ++tot;
	point[np].len = point[p].len+1;
	for(;p && !point[p].ch[c];p=point[p].fa) point[p].ch[c] = np;
	if(!p) point[np].fa = 1;
	else
	{
		int q = point[p].ch[c];
		if(point[q].len==point[p].len+1) point[np].fa = q;
		else
		{
			int nq = ++tot;
			point[nq] = point[q];
			point[nq].len = point[p].len+1;
			point[q].fa = point[np].fa = nq;
			for(;p && point[p].ch[c]==q;p=point[p].fa) point[p].ch[c] = nq;
		}
	}
}
char s[maxn];
int main()
{
	scanf("%s",s);
	int l = strlen(s);
	for(int i=0;i<l;i++) add(s[i]-'a');
	return 0;
}

多么美妙的模板

然后我们来分析一下本文的题目:

请你求出字符串的所有出现次数不为1的子串的出现次数乘上该子串长度的最大值。

那么怎么解决呢?

首先,有一个结论:后缀自动机中每个endpos集合的大小即该子串的出现次数。
而这个集合的大小是parent tree上儿子的大小。
如果我们设cnt[s]表示s的子树的大小,那么结果显然就是max(cnt[s]*point[s].len)
所以我们只需要在构造SAM的时候给终止节点标记为1,并从fa[i]向i连边跑dfs统计子树的大小就可以了。
代码如下:
注:吸氧才可过…

#include <bits/stdc++.h>
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 2e6+7;
struct node
{
	int ch[26];
	int len,fa;
}point[maxn<<1];
int las = 1,tot = 1,ans;
int cnt[maxn<<1]; 
vector<int> G[maxn<<1];
inline void add(char c)
{
	int p = las;
	int np = las = ++tot;
	cnt[tot] = 1;
	point[np].len = point[p].len+1;
	for(;p && !point[p].ch[c];p=point[p].fa) point[p].ch[c] = np;
	if(!p) point[np].fa = 1;
	else
	{
		int q = point[p].ch[c];
		if(point[q].len==point[p].len+1) point[np].fa = q;
		else
		{
			int nq = ++tot;
			point[nq] = point[q];
			point[nq].len = point[p].len+1;
			point[q].fa = point[np].fa = nq;
			for(;p && point[p].ch[c]==q;p=point[p].fa) point[p].ch[c] = nq;
		}
	}
}
void dfs(int s)
{
	for(int i=0;i<G[s].size();i++)
	{
		dfs(G[s][i]);
		cnt[s] += cnt[G[s][i]];
	}
	if(cnt[s]>1) ans = max(ans,cnt[s]*point[s].len); 
}
char s[maxn];
int main()
{
	scanf("%s",s);
	int l = strlen(s);
	for(int i=0;i<l;i++) add(s[i]-'a');
	for(int i=2;i<=tot;i++) G[point[i].fa].pb(i);
	dfs(1);
	printf("%d\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值