[APIO2014]回文串

V.[APIO2014]回文串

具体分析详见本人的SA题解,这里主要是讲解使用SAM求子串出现次数的方法。

SAM应用3:查询一个子串的出现次数。

这个思想很简单,只需要找到该子串对应的 \(\text{endpos}\) 等价类是哪个即可。

我们考虑记录 \(id_i\) 表示以位置 \(i\) 结尾的后缀所对应等价类的编号。然后,假设我们要查询子串 \([l,r]\),就找到 \(id_r\),然后往上倍增地跳父亲,直到跳到长度恰好不大于 \(r-l+1\) 的那个类,即为此子串所在的类。

使用马拉车求出所有极大回文串,复杂度 \(O\Big(n(\log n+|\Sigma|)\Big)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=300002;
typedef long long ll;
int cnt=1,ch[N<<1][26],fa[N<<1],len[N<<1],sz[N<<1];
int Add(int x,int c){
	int xx=++cnt;len[xx]=len[x]+1;
	sz[xx]=1;
	for(;x&&!ch[x][c];x=fa[x])ch[x][c]=xx;
	if(!x){fa[xx]=1;return xx;}
	int y=ch[x][c];
	if(len[y]==len[x]+1){fa[xx]=y;return xx;}
	int yy=++cnt;fa[yy]=fa[y];
	for(int i=0;i<26;i++)ch[yy][i]=ch[y][i];
	len[yy]=len[x]+1;
	fa[xx]=fa[y]=yy;
	for(;x&&ch[x][c]==y;x=fa[x])ch[x][c]=yy;
	return xx;
}
vector<int>v[N<<1];
void dfs(int x){for(auto y:v[x])dfs(y),sz[x]+=sz[y];}
int n,id[N];
ll val(int l,int r){
	l=max(l,0),r=min(r,n-1);
	int x=id[r];
	for(int i=18;i>=0;i--)if(ch[x][i]&&len[ch[x][i]]>=r-l+1)x=ch[x][i];
	return 1ll*sz[x]*(r-l+1);
}
char s[N],ss[N<<1];
int rad[N<<1];
ll res;
void Manacher(){
	int T=0,ret=0;
	ss[T++]='$';
	for(int i=0;i<n;i++)ss[T++]=s[i],ss[T++]='$';
	int Centre=-1,Rpos=-1;
	for(int i=0;i<T;i++){
		if(i<=Rpos)rad[i]=min(Rpos-i,rad[2*Centre-i]);
		while(i-rad[i]>=0&&i+rad[i]<T&&ss[i-rad[i]]==ss[i+rad[i]])rad[i]++;
		if(Rpos<i+rad[i])Centre=i,Rpos=i+rad[i];
		res=max(res,val((i-rad[i]+1)>>1,(i+rad[i]-2)>>1));
	}
}
int main(){
	scanf("%s",s),n=strlen(s);
	for(int i=0,las=1;i<n;i++)id[i]=las=Add(las,s[i]-'a');
	for(int i=1;i<=cnt;i++)ch[i][0]=fa[i],v[fa[i]].push_back(i);
	for(int j=1;j<=18;j++)for(int i=1;i<=cnt;i++)ch[i][j]=ch[ch[i][j-1]][j-1];
	dfs(1);
	Manacher();
	printf("%lld\n",res);
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14605682.html