【模板】manachar算法
推荐\参考题解博客:
https://zhuanlan.zhihu.com/p/70532099 本博客里面插入的图片是来自这篇回答,如有冒犯会立即删除
https://www.cxyxiaowu.com/2665.html
本题解主要为了加深自己体会,可能存在逻辑不通的地方,请读者加以指正:
预处理:
在对回文串进行判断的时候,有两种情况:
1是以单个字符为回文中心;
2是以两个字符中间间隙为回文中心;
如果在求回文子串时,每次对这两种情况进行判断,则会导致算法考虑情况过多以及复杂度比较大
一个很好的解决奇数和偶数问题的方法就是,在每个字符之间插入‘#’,并且为了使扩展过程中边界的处理判断,在两端分别插入'^'和‘$'(两个不在字符串中出现的字符),这样在判断两端字符是否相等时,到达边界就必定不会相等,从而跳出循环。进经过处理后的字符串长度永远为奇数。
计算辅助数组p
手动计算的方法是“中心扩散法”,此时记录以当前字符为中心,向两边同时扩展所能进行的最大步数。
关于辅助数组的结论:
辅助数组p的最大值就是 最长回文子串的长度
why?
简单理解:
1.新的回文中心是一个字符,那么在原回文子串的中心也是一个字符。在新的回文子串中,向两边扩散是:先分隔符,再字符。扩散的步数由于受到分隔符#的影响,扩散两步实际上只扫到一个有效字符,但是由于对称性,相当于在原始字符串中计算了两个字符。
2.如果新的回文中心为#,那么原始回文中心就是两个字符的间隙。在新的回文子串中,向两边扩散的特点是:先扫描到字符,然后是分隔符“,扩散的步数由于有分隔符#的作用,在新字符串中扩散两步,相当于在原始字符串中扫描到一个字符,但是相当于在原始字符串中计算了左右两个字符。
利用上面的处理,加上中心扩展的方法,时间复杂度是O(n^2),但是这是完全不够的。
Manachar算法,把原本O(n^2)的复杂度优化到线性O(n),极大提升了性能。
如何优化?
在上面的中心扩展中,一个不太智能的地方是:对于新字符串中每一个位置进行扩散,会导致原始字符串每一个字符都被访问多次,类似于KMP优化了前面不必要进行的部分,Manachar算法也是通过原先得到的辅助数组p以及回文串的对称性,将时间复杂度压缩到线性。
具体做法:
1.原字符串的下标:
用p的下标i减去p[i],再除以2,得到原字符串的开头下标
2.求每个p[i](重点):
用center表示回文串的中心,maxRight表示回文串的右边半径,所以maxRight = center + p[i]
注意:
一个center值对应唯一一个maxRight,因此,center和maxRight需要同时更新
当我们要求p[i]时,如下图:
i_mirror表示当前需要求的第i个字符关于center对应的下标
现在要求p[ii],如果是中心扩展法,就直接向两边扩展对比就行,但是也可以利用回文串的对称性:
i关于c的对称点是i_mirror,p[i_mirror]=3,所以p[i]=3;
但是有三种情况会造成直接复制P[i_mirror]是不正确的:
1.超出了maxRight
如上图情况,在我们求p[i]的时候,p[i_mirror]=7,但是此时p[i]并不等于7.
当我们从i开始往后数7个,等于22,已经超过了maxRight的范围,此时就不能利用对称性了。
但是此时我们一定能扩展到maxRight的位置,所以p[i]至少等于maxRight-i
之后我们任然需要比较T[maxRight+1]和它关于i的对称点,然后像中心扩展法一样一个个去扩展
2.p[i_mirror]遇到了原字符串的左边界
此时p[i_mirror]=1, 但是p[i]赋值称1是不正确的
这种情况是因为p[i_mirror]在扩展的时候首先是"#" == "#",之后遇到了"^"和另一个字符比较,遇到了边界,然后终止了循环
但是p[i]并没有遇到边界,所以我们可以继续通过中心扩展法去向两边进行扩展
3.i等于了maxRight
此时我们先把p[i]赋值为0,然后通过中心扩展法一步步扩展就好
4,考虑center和maxRight 的更新
一步步求初每个p[i], 当求初p[i]的右边界大于当前maxRight时,就需要更新center和maxRight为当前的回文串了
我们必须保证 i 在 maxRight 里面,所以右更右边的maxRight就要更新maxRight
整篇思路理清下来,我觉得优化思想和KMP相类似, 都是直接去除重复的判断步骤来进行优化
具体代码如下:
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int maxn=32000005; char s[maxn],st[maxn]; int p[maxn]; int change() { int len=strlen(st); int j=2; s[0]='^'; s[1]='$'; for (int i=0;i<len;i++) { s[j++]=st[i]; s[j++]='$'; } s[j]='&'; return j; } int Manacher() { int len=change(),mid=1,mx=1,ans=-1; for (int i=1;i<len;i++) { if (i<mx) p[i]=min(mx-i,p[mid*2-i]); else p[i]=1; while (s[i-p[i]]==s[i+p[i]]) p[i]++; if (mx<i+p[i]) { mid=i; mx=i+p[i]; } ans=max(ans,p[i]-1); } return ans; } int main() { scanf("%s",st); printf("%d",Manacher()); }