题解:【模板】后缀自动机(SAM)

题目传送门

后缀数组偏技巧,一般都需要和其他的数据结构或算法结合使用,但是同时也更加灵活,可以解决更多更复杂的问题。

后缀自动机偏暴力,一般问题可以直接无脑上,但是对于复杂的问题可能会无能为力。

伟大的 lcx 曾经说过:如何学习后缀数组?只需要把所有 SAM 的题目做一遍就可以了。

题意分析

发现我们不会 SAM,考虑 SA 做法。

考虑枚举子串长度 \(\textit{len}\)。若 \(\textit{height}_i\geq\textit{len}\),说明 \(s[\textit{sa}_{i-1},n],s[\textit{sa}_i,n]\) 的公共前缀长度已经 \(\geq\textit{len}\),在 \(\textit{len}\) 变小的时候不变。

因此考虑从大到小枚举 \(\textit{len}\)\(\textit{height}_i\geq\textit{len}\) 说明 \(s[\textit{sa}_{i-1},n],s[\textit{sa}_i,n]\) 在接下来的过程中完全相同,可以并查集合并到一起维护。

再特判一下子串出现次数为 \(1\) 即可。

AC 代码

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e6,V=127;
int n,sa[N+1],rk[N<<1|1],height[N+1];
char s[N+1+1];
void buildSA(){
	static int sa0[N+1],rk0[N+1],cnt[N+1];
	for(int i=1;i<=n;i++){
		cnt[rk[i]=s[i]]++;
	}
	for(int i=1;i<=V;i++){
		cnt[i]+=cnt[i-1];
	}
	for(int i=1;i<=n;i++){
		sa[cnt[rk[i]]--]=i;
	}
	for(int i=1;i<=n;i++){
		rk0[i]=rk[i];
	}
	for(int p=0,i=1;i<=n;i++){
		if(rk0[sa[i]]==rk0[sa[i-1]]){
			rk[sa[i]]=p;
		}else{
			rk[sa[i]]=++p;
		}
	}
	for(int w=1;w<n;w<<=1){
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++){
			sa0[i]=sa[i];
		}
		for(int i=1;i<=n;i++){
			cnt[rk[sa0[i]+w]]++;
		}
		for(int i=1;i<=n;i++){
			cnt[i]+=cnt[i-1];
		}
		for(int i=n;1<=i;i--){
			sa[cnt[rk[sa0[i]+w]]--]=sa0[i];
		}
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++){
			sa0[i]=sa[i];
		}
		for(int i=1;i<=n;i++){
			cnt[rk[sa0[i]]]++;
		}
		for(int i=1;i<=n;i++){
			cnt[i]+=cnt[i-1];
		}
		for(int i=n;1<=i;i--){
			sa[cnt[rk[sa0[i]]]--]=sa0[i];
		}
		for(int i=1;i<=n;i++){
			rk0[i]=rk[i];
		}
		for(int p=0,i=1;i<=n;i++){
			if(rk0[sa[i]]==rk0[sa[i-1]]&&rk0[sa[i]+w]==rk0[sa[i-1]+w]){
				rk[sa[i]]=p;
			}else{
				rk[sa[i]]=++p;
			}
		}
	}
}
void buildHeight(){
	for(int i=1,k=0;i<=n;i++){
		k=max(k-1,0);
		while(s[i+k]==s[sa[rk[i]-1]+k]){
			k++;
		}
		height[rk[i]]=k;
	}
}
struct dsu{
	int f[N+1],size[N+1],Max;
	int find(int x){
		if(f[x]!=x){
			return f[x]=find(f[x]);
		}
		return x;
	}
	void merge(int x,int y){
		x=find(x),y=find(y);
		if(size[x]<size[y]){
			f[x]=y;
			size[y]+=size[x];
			Max=max(Max,size[y]);
		}else{
			f[y]=x;
			size[x]+=size[y];
			Max=max(Max,size[x]);
		}
	}
	void build(int n){
		for(int i=1;i<=n;i++){
			f[i]=i;
			size[i]=1;
		}
		Max=1;
	}
	int query(){
		if(Max==1){
			return 0;
		}
		return Max;
	}
}dsu;
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	cin>>(s+1);
	n=strlen(s+1);
	buildSA();
	buildHeight();
	ll ans=0;
	dsu.build(n);
	priority_queue<pair<int,int>>q;
	for(int i=1;i<=n;i++){
		q.push({height[i],i});
	}
	for(int len=n;1<=len;len--){
		while(q.size()&&q.top().first>=len){
			int i=q.top().second;
			dsu.merge(sa[i],sa[i-1]);
			q.pop();
		}
		ans=max(ans,1ll*len*dsu.query());
	}
	cout<<ans<<'\n';
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-07-23 14:43  TH911  阅读(1)  评论(0)    收藏  举报