manacher算法

manacher,又叫马拉车。算法

马拉车能够用来求最长回文子串。数组

思考如何求最长回文子串,咱们首先会想到一个 \(O(n^3)\) 的解法:枚举全部子串,而后判断是否为回文串。固然这个解法很是大 蠢。
而后,咱们又会有一个想法,枚举每一个点,而后从这个点向外拓展,若是相同则拓展,不然记录答案。这里有个问题,就是回文串的长度多是偶数,也就是没有中间点,那么咱们能够在每两个字符之间添加 '#',这样就有了中间点了,像这样:

注意,前面的 '@' 是防止数组越界的,若是扫到头尾都是回文串,那么若是继续扫下去就会越界,而这里有一个 '@' 就能够防止越界的发生。
那么此时这个回文串真正的长度应该为 半径(包含中间点)-1。
这个算法的时间复杂度是 \(O(n^2)\)的。
咱们思考上面那个算法,发现他有许多位置是重复枚举的,好比:

橙色线中间部分是一个回文串,红色线中间部分也是一个回文串,圈起来的是回文串的中心。
能够发现,这两个回文串会有重复的地方,那么有没有方法避免这些重复枚举呢?答案是有的。
首先,咱们记录一个最大的 \(r\) 和对应的 \(mid\),使得 \(mid - (r - mid)\)~\(r\) 是一个回文串。(这里的 \(mid\)其实就是回文串中心的位置啦)
而后,若咱们当前查询的节点 \(now\)\(r\) 以内:

那么,既然在一个回文串的右半部分(由于是从前日后扫,因此必定在 \(mid\) 右边,是有半部分),则必定有一个对应的左半部分的字符(下面简称 \(lc\) QwQ):

\(lc\) 的半径是已经求出的,若 \(lc\) 的半径是在这个大回文串以内,那么 \(now\) 半径就和 \(lc\) 的同样。但若是以 \(lc\) 为中心的字符串不全在大回文串之中呢?好比:

那么,此时咱们能肯定的半径就是 \(r-now\),也就是这一段:

那么,咱们只要在上面两个中取一个最小值便可。spa

code:code

#include<cstdio>
#include<string>
using namespace std;
string s;
int mid,r,ans,b[22000005];//b[i]记录以i为中心的回文串的半径
int min(int x,int y){return x<y?x:y;}
void read(string &ss)
{
	ss="@#";
	char ch=getchar();
	while(ch<'a'||ch>'z') ch=getchar();
	while(ch>='a'&&ch<='z') ss+=ch,ss+='#',ch=getchar();
	return ;
}
int main()
{
	read(s);
	for(int i=1;i<(int)s.length();i++)
	{
		if(i<=r) b[i]=min(b[mid*2-i],r-i+1);
		while(s[i-b[i]]==s[i+b[i]]) b[i]++;
		if(i+b[i]-1>r) r=i+b[i]-1,mid=i;//更新r和mid
		if(b[i]-1>ans) ans=b[i]-1;
	}
	printf("%d\n",ans);
	return 0;
}
相关文章
相关标签/搜索