Manacher(马拉车)

Manacher:在时间复杂度为O(n)求一段字符串的最长回文串的长度。

先了解几个概念:
最右回文子串
即回文子串的右边界最右的子串。

以下最长子串 即表示 最右回文子串
并且我们需要一个辅助数组 f [ ] ,f [ i ]表示以 i 为中心的回文串的长度
那么接下来,我们就要用已知的 f [ j ] 求 f [ i ]。
那么接下来 我们就可以利用最长的回文串作利用信息。
分为几个情况;
(最长回文串的右边界为R,左边为L,中心为P)
第一种:假设 i 在最长回文子串的里面,j是 i 关于P点对称的点。
并且J为中心的回文串在最长回文串里面,那么我们可以O(1)求得
f [ i ] = f [ j ],因为在这回文串里面 是对称的
Manacher(马拉车)_第1张图片

第二种:
以J为中心的回文串刚好贴近 L 左边界,那么我们求f [ i ]只能暴力得看R右边是否和 i - d (d表示以 i 为中心的回文串的长度)相匹配
Manacher(马拉车)_第2张图片

第三种:
以J为中心的回文串有一部分不在最长回文串里面。那么此时
我们仅看 J 的左边界为 L,其长度为 J-L+1,那么f [ i ]=J-L+1。
此时 i 不可向右扩展。
证明:如果 字符串 R+1 == i-(J-L) ,那么根据对称性,i-(J-L) == J+(J-L)
J+(J-L) == L-1,这样的话L-1==R+1,那么这样的话,这个子串就不是最右回文子串了,与题意相矛盾。
Manacher(马拉车)_第3张图片
最后一种就是 i 不在最长回文子串里面,那么我们没什么可利用的了,就直接暴力扩展。

对于偶数串,奇数串,我们在处理时可在字符间加入’#’,在开头和结尾加入不同的字符“ $ ”,“@”这样可以让我们处理时方便许多,边界容易处理。
如’abc’这样的字符串处理后变为’$#a#b#c#@’。

Manacher模板:

void manacher(string s){
	vector<int> ans,lef,str;
	int n=s.length(),m=(n+1)<<1,ret=0;
	str.resize(m+1);lef.resize(m+1);ans.resize(m+1);
	//开头和结尾分别为:'$' ,'@';
	str[0]='$';str[1]='#';str[m]='@';
	//在字符串中间插入'#' 
	for(int i=1;i<=;i++)
		str[i<<1]=s[i-1],str[i<<1|1]='#';
	ans[1]=1;
	for(int r=0,p=0,i=2;i<m;i++){
		if(r>i)	ans[i]=min(ans[p*2-i],r-i);
		else ans[i]=1; 
		while(str[i-ans[i]]==str[i+ans[i]])	ans[i]++;	//暴力向外匹配 
		if(i+ans[i]>r)	r=ans[i]+i,p=i;					//更新最右边界及其中点 
		ret=max(ret,ans[i]-1);
	}
	//lef[i]数组表示 以 i 为起点的最长回文子串的中点在哪 
	for(int i=0;i<=m;i++)	lef[i]=0;
	for(int i=2;i<m;i++)	lef[i-ans[i]+1]=max(left[i-ans[i]+1],i+1);
	for(int i=2;i<=m;i++)	lef[i]=max(lef[i],lef[i-1]);				//可以将回文信息扩展 
}
int left(int x){							//求以x为起点的最长回文子串的长度 
	return lef[(x+1)<<1]-((x+1)<<1);
}
int mid(int x,int odd){						//求以x为中点的最长回文子串的长度 
	if(odd)	return ans[(x+1)<<1]-1;
	return ans[(x+1)<<1|1]-1;
}

你可能感兴趣的:(Manacher(马拉车))