Manacher (马拉车)算法

Manacher于1975年发现了一种线性时间算法,可以在列出给定字符串中从任意位置开
始的所有回文子串
。同样的算法也可以在任意位置查找全部极大回文子串,并且时间复杂
度是线性的。那他是怎样实现的呢,别着急继续往下看。

我们先看一下回文串,回文串有两种形式,一种是奇数的比如"aba",一种是偶数的比
如"abba"。这里使用Manacher算法的时候,会在每个字符之间都会插入一个特殊字
符,并且两边也会插入,这个特殊字符要保证不能是原字符串中的字符,这样无论原来字
符串长度是奇数还是偶数,添加之后长度都会变成奇数
。例如
"aba"-->"#a#b#a#"(长度是7)
"abba"-->"#a#b#b#a#"(长度是9)
这里再来引用一个变量叫回文半径,通过添加特殊字符,原来字符串长度无论是奇数还是
偶数最终都会变为奇数,因为特殊字符的引用,改变之后的字符串的所有回文子串长度一
定都是奇数。并且回文子串的第一个和最后一个字符一定是你添加的那个特殊字符。其实
很好证明:
如果原来回文子串的长度是奇数,通过中间插入特殊字符,特殊字符的个数必定是
偶数,在加上两边的特殊字符,长度必然是奇数
如果原来回文子串的长度是偶数,通过中间插入特殊字符,特殊字符的个数必定是
奇数,在加上两边的特殊字符,长度必然是奇数

因为添加特殊字符之后所有回文子串的长度都是奇数,我们定义回文子串最中间的那个字
符到回文子串最左边的长度叫回文半径

 假如以当前字符s[maxCenter]为回文中心的最大回文长度是从left到maxRight,如
下图所示
如果我们想求以字符s[i]为回文中心的最大回文长度,我们只需要找到i关于maxCenter
的对称点j,看下j的回文长度,因为j已经计算过了。
1,如果i在maxRight的左边,并且j的最大回文长度左边没有到达left,根据对称性,i
的最大回文长度就等于j的最大回文长度,

2.如果i在maxRight的左边,并且j的最大回文长度左边到达或者超过left,根据对称
性,i的最小回文长度等于j-left也等于maxRight-i,至于最大能有多大,还需要在继
续判断

3.如果i在maxRight的右边,我们就没法利用之前计算的结果了,这个时候就需要一
个个判断了

举个栗子:

 

 for (int i = 0; i < length; i++) {
 if (i < maxRight) {
 //情况一,i没有超出范围[left,maxRight]
 //2 * maxCenter - i其实就是j的位置,实际上是判断p[j]<maxRight - i
 if (p[2 * maxCenter - i] < maxRight - i) {
 //j的回文半径没有超出范围[left,maxRight],直接让p[i]=p[j]即可
 p[i] = p[2 * maxCenter - i];
 } else {
 //情况二,j的回文半径已经超出了范围[left,maxRight],我们可以确定p[i]的最小值
 //是maxRight - i,至于到底有多大,后面还需要在计算
 p[i] = maxRight - i;
 //继续计算
 while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
 p[i]++;
 }
 } 
    else {
 //情况三,i超出了范围[left,maxRight],就没法利用之前的已知数据,而是要一个个判断了
 p[i] = 1;
 //继续计算
 while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
 p[i]++;
 }
 }

当然这个代码可以进行简化具体如何实现我们会在接下来的题中体现: 

 下面我们在题中试验一下:

1.题目引入: 

One day, sailormoon girls are so delighted that they intend to research about palindromic strings. Operation contains two steps: 
First step: girls will write a long string (only contains lower case) on the paper. For example, "abcde", but 'a' inside is not the real 'a', that means if we define the 'b' is the real 'a', then we can infer that 'c' is the real 'b', 'd' is the real 'c' ……, 'a' is the real 'z'. According to this, string "abcde" changes to "bcdef". 
Second step: girls will find out the longest palindromic string in the given string, the length of palindromic string must be equal or more than 2.

Input

Input contains multiple cases. 
Each case contains two parts, a character and a string, they are separated by one space, the character representing the real 'a' is and the length of the string will not exceed 200000.All input must be lowercase. 
If the length of string is len, it is marked from 0 to len-1.

Output

Please execute the operation following the two steps. 
If you find one, output the start position and end position of palindromic string in a line, next line output the real palindromic string, or output "No solution!". 
If there are several answers available, please choose the string which first appears.

2.样例输出: 

Sample Input

b babd
a abcd

Sample Output

0 2
aza
No solution!

题目大意:如: “abc de”,但内部‘a’不是真实的‘a’,这意味着如果我们定义‘b’是真实的‘a’,那么我们就可以推断‘c’是真实的‘b’,‘d’是真实的‘c’…… 如果原字符串存在回文串则输出最长回文串的起始和终止位置,并输出该回文串,如果不存在则输出 No solution!

3.代码如下: 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2e6+10;
char str[maxn];
char str1[maxn<<1];
int p[maxn<<1];
int init()
{
	int len=strlen(str);
	str1[0]='$';       //注意令这个新串下真正的回文从 1 开始的小技巧 
	str1[1]='#';
	int j=2;
	for(int i=0;i<len;i++)
	{
		str1[j++]=str[i];
		str1[j++]='#';
	}
	str1[j]='#';
	return j;
}
int Manacher()
{
	int len=init();
	int id;      //表示最大回文串的中心下标 
	int maxx=0;  //表示最大右边界 
	for(int i=1;i<len;i++)
	{
		if(i<maxx) p[i]=min(p[2*id-i],maxx-i); //这是前两种情况的合并   
		/*即: i没有超出范围[left,maxx]
 		 maxCenter - i其实就是j的位置,实际上是判断p[j]<maxx - i,直接让p[i]=p[j]即可
	    情况二:  j的回文半径已经超出了范围[left,maxx],我们可以确定p[i]的最小值
		          是maxx - i,至于到底有多大,后面还需要在计算
		*/
		else p[i]=1;			 //情况三,i超出了范围[left,maxx],就没法利用之前的已知数据,而是要一个个判断了
		while(str1[i-p[i]]==str1[i+p[i]]) p[i]++;
		//记录最长回文右边界和和这个最长回文串中心位置下标 
		if(maxx<i+p[i])
		{
			maxx=i+p[i];  // 当i点加上它的回文半径更大时更新最右边界
			id=i;			//并记录中心位置 
		}
	}
	return len; 
}
int main()
{
	ios::sync_with_stdio(false);  //可以增加运算速度 
	char c;
	while(cin>>c)          // 表示 'a' 实际对应 c 
	{
		cin>>str;
		int len=strlen(str);   //字符串的原始长度 
		for(int i=0;i<len;i++)   // 将字符串中的字符转换成实际字符 
		{
			str[i]-=(c-'a');     //每个字符减去实际的两个字符的 
			if(str[i]<'a') str[i]+=26;  //保证字符循环循环 
		}
		len=Manacher();   
		int ans=0;
		int x,y;
		for(int i=1;i<len;i++)     //记录最长回文串的半径和中心位置 
		{
			if(p[i]>ans)
			{
				ans=p[i];
				x=i;
			}
		}
		if(ans==2) cout<<"No solution!"<<endl;   // 如果没有回文串那么新的字符串中返回最大的回文半径为 2 
		else
		{
			int l=(x-ans)/2;       // 注意原回文我们是从 0 开始的 
			int r=(x+ans)/2-2;
			cout<<l<<' '<<r<<endl;  //后面输出就行 
			for(int i=l;i<=r;i++)    
			{
				cout<<str[i];
			}
			cout<<endl;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风遥~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值