初学Manacher(马拉车)算法

例题见洛谷P3805:洛谷P3805

一个大神的博客:http://www.cnblogs.com/grandyang/p/4475985.html

暴力

n^3:枚举每一个子串,然后判断是不是回文的。

n^2:枚举每个点,以该点向左右同时扩展。

Manacher

>>首先搬一下这个算法的原理,主要是利用了回文子串中,左右部分划分出的子串仍有对称性的特点,尤其是利用了长度相等。

 

>>以下定义记住就行:

1,若原串为s,设p[i]表示算上s[i]时,以s[i]为中心扩展的回文串最大半径。例如:aaacaaa,c对应的p[i]就是4。

2,设maxright为字符串向右到达过的最远的下标,mid表示最长回文子串的对称点,i则是当前枚举到的字符下标。可以想到,mid和maxright是同时更新的。为了帮助理解,设一个opposite,表示maxright关于mid的对称点;设一个j,表示i关于mid的对称点,可以算得j=mid*2-i。

 

>>然后开始分析:

1,回文串长度有奇偶,解决方法是在两个字符之间插入一个原串没有的字符,比如:

aaacaaa  -  #a#a#a#c#a#a#a#   。

如此一来所有的回文串长度都是奇数。在首尾插入是为了方便操作。可以发现,p[i]-1就是以i为中心的最长回文子串长度(去掉#)。

 

2,求p[i]的式子为:

                p[i]=(maxright>i\ ?)\ min\{p[mid*2-i],\ maxright-i\}:1

这是该算法的核心所在,下面将画图理解

**情况1:  maxright > i,则以s[i]为对称中心的最大回文串半径一定包括在或者相交于mid~maxright这一段。又分两种情况。

这种情况下,以j和以i为对称点的最长回文子串完全包括在里面,因为mid两边对称的关系,所以p[i]=p[j]=p[mid*2-i]。

这种情况下,并未完全包含,但至少可以保证i~maxright这一段是可取的。于是取到maxright-i。如果还有一段,就直接向两边拓展直到不匹配为止。

**情况2:i超过了maxright,那么直接赋初值为1,表示自己本身是一个回文子串。

3,其它变量的更新。上面的式子解决后其它的便不成问题。

首先通过上面的式子初步得到的p[i]还可能继续拓展,所以一个while来继续更新,得到最后的p[i]。然后可以比较maxright和p[i]+i-1来更新maxright和mid;同时更新答案。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN=11000005;
int p[MAXN*2],N=0;
char s[MAXN*2],c[MAXN];

void solve()
{
	scanf("%s",c);
	int len=strlen(c),i=0;
	s[N]='$';s[++N]='#';
	while(i<len)
	{
		s[++N]=c[i];
		s[++N]='#';
		i++;
	} s[N+1]='\0';  //插入其他字符 
	int mid=0,maxright=0,ans=0;
	for(int i=1;i<=N;i++)
	{
		p[i]=i>maxright?1:min(p[mid*2-i],maxright-i);
		while(s[i+p[i]]==s[i-p[i]]) p[i]++;  //向左右拓展 
		if(p[i]+i-1>maxright)
		{
			maxright=p[i]+i-1;
			mid=i;
		}
		ans=max(ans,p[i]-1);
	}
	cout<<ans;
}

int main()
{
	solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81484548