Manacher计算回文子串

Manacher算法

题目描述 给出一个只由小写英文字符组成的字符串 SS ,求 SS 中最长回文串的长度 。
字符串长度为 nn
输入格式 一行小写英文字符 a,b,c,…y,z 组成的字符串 SS。

输出格式 一个整数表示答案。

输入输出样例 输入 #1 aaa
输出 #1 3

求最长回文子串的传统思路是,遍历每一个字符,以该字符为中心向两边暴力查找。其时间复杂度为O(n2),效率很低。1975年,一个叫Manacher的人发明了一个算法,Manacher算法(俗称:马拉车算法),该算法可以把算法效率提升到O(n)。

首先因为序列要分为奇序列与偶序列,为了避免讨论,认为转换一下,变为奇序列。具体做法是:在字符串首尾,及各字符间各插入一个字符(插入的这个字符是原串中没有的)。

eg1

   插入的是同样的符号,且符号不存在于原串,因此子串的回文性不受影响。
   原来是回文的串,插完之后还是回文的,
   原来不是回文的,依然不会是回文。调整后,串的长度都转换成了奇数。

我们把一个回文串中最左或最右位置的字符与其对称轴的距离称为回文半径。Manacher定义了一个回文半径数组R,用 R[i] 表示以第i个字符为对称轴的回文串的回文半径。
在这里插入图片描述
可以看出,R[i] - 1正好是原字符串中以i为中心的最长回文串的长度。
原串的最长回文串长度为max{ R[i]-1 }
于是问题变成了,怎样高效地求的R数组。
**

基本思路是利用回文串的对称性,扩展回文串。

**
我们从左往右依次讨论以每个字符为中心的回文半径的长度。我们设MaxLen表示,当前已讨论过的回文子串中,最右边能到达的位置,同时用P记录下对应回文子串的中心所在位置。
在这里插入图片描述
红色表示p对应的R[i],黑色表示p。
我们从左往右依次求每个字符的R[ ],假设当前访问到的位置为i,即要求R[i],在对应上图,i必然是在P右边的。但我们更关注的是,i是在MaxLen的左边还是右边。我们需要分情况来讨论。

i<maxlen

在这里插入图片描述
由于对称性,我们可以推出来R[i],是不会小于关于p的对应点R[2p-i]的(**如果i +r[2p-i]<maxlen**),但是也有可能R[2*p-i]很大,这个时候R[i]就只能取到maxlen-i+1;

i>maxlen

这个时候显然R[i]=1;
最后一步我们就要更新maxlen和对应的p值。
**代码如下 **

#include<bits/stdc++.h>
using namespace std;
char ss[100009];
char ch[200009];
int r[500009];
int inti()
{
    
    
	int len=strlen(ss);
	int j=0;
	ch[0]='@';
	ch[1]='#';
	j=2;
	for(int i=0;i<len;i++)
	{
    
    
		ch[j++]=ss[i];
		ch[j++]='#';
	}
	ch[j]='\0';
//	for(int i=1;i<=j;i++)
//		putchar(ch[i]);
//	cout<<endl;
	return j;
}
int manachar()
{
    
    
	int len=inti();
	int p=0,maxlen=0;

	for(int i=1;i<=400000;i++)
		r[i]=0;

	for(int i=1;i<len;i++)
	{
    
    
		if(i<maxlen)r[i]=min(r[2*p-i],maxlen-i+1);
		else r[i]=1;//最重要的优化
		while(ch[i-r[i]]==ch[i+r[i]])r[i]++;//更新R[i]
		if(maxlen<i+r[i]-1)//更新相应的maxlen和p
		{
    
    
			maxlen=i+r[i]-1;
			p=i;
		}
	}
	return len;
}

猜你喜欢

转载自blog.csdn.net/m0_50089378/article/details/109294188
今日推荐