题解 P3805 【【模板】manacher算法】
codesonic
2017-09-09 23:49:21
考虑判断回文的方法
1.暴力
最朴素的暴力
举出该字符串的所有子串,判断其是否回文,时间复杂度是$O(n^3)$的
2.改进第一种,仍然是暴力
所有的回文串都是对称的。长度为奇数回文串以最中间字符的位置为对称轴左右对称,而长度为偶数的回文串的对称轴在中间两个字符之间的空隙。可以遍历这些对称轴,在每个对称轴上同时向左和向右扩展,直到左右两边的字符不同或者到达边界。
时间复杂度$O(n^2)$
3.manacher算法
首先我们可以先查看第二种方法有哪些缺点
1)回文串长度的奇偶性造成了对称轴的位置可能在某字符上,也可能在两个字符之间的空隙处,第二种方法要对两种情况分别处理
那么manacher对此的优化是这样的
在每两个字符中间插入另一个字符,如'#'。
也就是说,原来的字符串是这样的
![](https://cdn.luogu.com.cn/upload/pic/7882.png)
现在的字符串是这样的(为了不越界以及方便,a[0]设置为‘#’)
![](https://cdn.luogu.com.cn/upload/pic/7883.png)
那么这个缺点解决了
2)会出现很多子串被重复多次访问,时间效率大幅降低。
这里比较难理解
用一个辅助数组hw表示每个点能够扩展出的回文长度
我们从s[1]遍历到s[strlen(s)],循环变量为i
我们先设置一个辅助变量maxright,表示已经触及到的最右边的字符
另外还要设置一个辅助变量mid,表示包含maxright的回文串的对称轴所在的位置
也就是这样:
![](https://cdn.luogu.com.cn/upload/pic/7884.png)
当i在maxright左边且在mid右边时:
设i关于mid的对称点为j,显然hw[i]一定不会小于hw[j]。(对称)
我们没必要保存j,j可以通过计算得出,为:
$(mid<<1)-i$,可以尝试自己算
那么我们就设置hw[i]=hw[j]然后继续尝试扩展,这样就可以较快地求出hw[i],然后更新maxright和mid
当i在maxright右边时,我们无法得知关于hw[i]的信息,只好从1开始遍历,然后更新maxright和mid
这样就是这个算法的全部了,如果认真读了是不会太难的,实在不行可以百度。
而该算法的时间复杂度和空间复杂度都是线性的
小结:manacher就是利用回文对称的性质,来简化求以一个字符为对称轴的最大的回文长度。别看这简化不大,当数据很大时,这样求的速度会快很多。
下面是代码:
```cpp
#include<iostream>
#include<cmath>
#include<cstring>
#define maxn 51000100
using namespace std;
int n,hw[maxn],ans;
char a[maxn],s[maxn<<1];
void manacher()
{
int maxright=0,mid;
for(int i=1;i<n;i++)
{
if(i<maxright)
hw[i]=min(hw[(mid<<1)-i],hw[mid]+mid-i);
else
hw[i]=1;
for(;s[i+hw[i]]==s[i-hw[i]];++hw[i]);
if(hw[i]+i>maxright)
{
maxright=hw[i]+i;
mid=i;
}
}
}
void change()
{
s[0]=s[1]='#';
for(int i=0;i<n;i++)
{
s[i*2+2]=a[i];
s[i*2+3]='#';
}
n=n*2+2;
s[n]=0;
}
int main()
{
scanf("%s",a);
n=strlen(a);
change();
manacher();
ans=1;
for(int i=0;i<n;i++)
ans=max(ans,hw[i]);
printf("%d",ans-1);
return 0;
}
```
如果哪里有问题可以在评论提出,谢谢orz