如果要求一个回文串:传(bao)统(li)算法就是:遍历每个字符,以该字符为中心向两边查找。时间复杂度为O(n^2);
而对于Manacher算法可以将时间复杂度降为O(n);
我们都知道,回文串有偶回文和奇回文之分,例如,abba,abcba,如果依照这种方法我们需要分情况讨论,现在我们对要处理的字符串处理一下,避免这种情况的讨论;
对于字符串:s=‘abbadcacda’
我们可以进行如下处理:s_new=’$#a#b#b#a#d#c#a#c#d#a#\0’
此时无论是奇回文还是偶回文,都统一转化成了奇回文的形式
s_new=’$#a#b#b#a#d#c#a#c#d#a#\0’
表格为以i为中心的最长回文半径(p[i])
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
s_new[i] | $ | # | a | # | b | # | b | # | a | # | d | # | c | # | a | # | c | # | d | # | a | # | ‘\0’ |
p[i] | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 6 | 1 | 2 | 1 | 2 | 1 | 2 | 1 |
而字符串中最长回文半径就是p[i]-1
现在问题来了,如何求p[i]呢
定义两个变量:right和mid。
其中right是以mid为中心的最长回文右边界right=mid+p[i])
if(i<right) p[i]=min(p[2*mid-i],right-i); right-i是防止超出边界
刚开始不理解这个式子:想一个问题,现在以i为中心的最长回文串已经找到了,而且right(回文串右边界)我们也标记好了,回文串左右两边实对称的,我们现在找这个是不是可以直接查询中点之前的对称的那一个点的p[i]的值是不是就好了,不用执行while循环去再次计算了。而2*mid-i就是i关于mid对称的前面的那一个下标
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int N=111000;
char s[N];
char s_new[N*2];
int p[N*2];//一定是二倍,别忘了啊啊,不然会超时。。。竟然不是re
int init()
{
int j=0;
s_new[j++]='$';
s_new[j++]='#';
for(int i=0;s[i];i++)
{
s_new[j++]=s[i];
s_new[j++]='#';
}
s_new[j]='\0';
return j;
}
int Manacher()
{
int len=init();
int mid;
int right=0;
int max_len=-1;
for(int i=1;i<len;i++)
{
if(i<right) p[i]=min(p[2*mid-i],right-i);
else p[i]=1;
while(s_new[i-p[i]]==s_new[i+p[i]]) p[i]++;
if(right<p[i]+i) right=p[i]+i,mid=i;
max_len=max(max_len,p[i]-1);
}
return max_len;
}
int main()
{
while(~scanf("%s",s))
{
printf("%d\n",Manacher());
}
return 0;
}