首页 > 代码库 > Manacher算法详解
Manacher算法详解
/*首先呢,手动@一下逸安dalao。前几天一是考试没时间,再就是这个算法我只是懂,没写过题所以不敢写一个专题博客。
终于抽出时间写一下这个noip基本不会涉及的算法啦*/
首先我们知道回文的定义:正反读都是一样的字符串叫做回文串。如:madam,lol,oppo,zz,甚至连单字符都可以被称为回文(串)。上面的例子可以看出,回文串可以分为两种:奇数回文和偶数回文。
如果我们想知道以一个字符串的所有子串中,最长的回文串是多长,怎么办呢?
首先想到用裸的枚举中心,然后扩展边界方法来求回文(比如对于hellomadams以d为中心,首先a=a,然后m=m,再然后o!=s找到以d为中心最长回文),我们就会发现第一个难题:偶数回文串并没有以字符为中心。那怎么扩展边界呢?
有一种特别巧妙的方法解决了这个问题。如下:
比如oppovivo这个串,我们在每两个字符之间加上一个特殊符号,通常用#来充当,这样变为了o#p#p#o#v#i#v#o。
那么oppo成为了以第二个‘#’为中心的回文,viv仍是以i为中心的回文。同时我们在首尾加上两个不同的特殊字符,通常就用‘@’,‘$‘。这样连边界的判断都省了不是~到了首或尾,一定会因为字符不同结束扩展。
这部分的代码给一下。
void init(int n){
s[0]=‘@‘;
for(int i=1;i<=n;i++){
s[i*2-1]=str[i-1];
s[i*2]=‘#‘;
}
s[n*2]=‘$‘;
}
str是读进来的串,s是修改后的。
以上部分仅是铺垫manacher的精髓在于:充分利用已有条件,使复杂度降为O(n)。
以下图片均来自于博客http://blog.csdn.net/dyx404514/article/details/42061017
首先,我们要求什么:len数组,len[i]表示以s[i]为中心的最长回文串半径是多少。str转换为s后,所有回文长度一定是奇数。
我们还需要两个变量Po,mx。由于是递推求解,mx表示之前计算中最长回文子串的右端点的最大值,Po表示取得这个最大值的中点。在图中P就是上述mx。
情况一:
i是接下来要求len值的点。设i关于Po的对称点是j,即j=2Po-i。
(1) i+len[j] < mx.
如图。
因为i是在当前端点最靠右的回文串之内,所以他一定有一个关于Po的对称点j。i、j位置上的字符一定是一样的。
不仅如此,以j为中心的最大回文串与以i为中心的最大回文串也一定是一样的。len[j]已知,如果i的半径比len[j]大,哪怕是一个字符,根据他们关于Po对称且不在[2Po-mx,mx]范围外,len[j]就还能扩展,len[j]的求解就是矛盾的。
所以此时len[i]=len[j]即可。
(2) i+len[j] >= mx
如图。
与上一种情况同理。在[2Po-mx,mx]范围内的,以i,j为中心的部分是相等的。而边界之外则是一定不同的,否则mx还能更大。
这时候边界以外就好好在扩张一遍,mx外面可能还有i的回文串,别忘了更新Po,mx。
情况二:
i > mx
啥也不用说了,已经得到的信息卵用没有,老实儿的重新枚举扩展。
这部分的代码:
void manacher(int n){ int mx=0,po=0; for(int i=1;i<n*2;i++){//首尾的字符不用管,也不能管。要不该越界了。 if(mx>i) f[i]=min(f[po*2-i],mx-i); else f[i]=1; while(s[i-f[i]]==s[i+f[i]]) f[i]++; if(f[i]+i>mx) mx=f[i]+i,po=i; } }
总结
这方面的题还是比较好做,不用涉及太多其他的算法。
最多是在字符串上跑动归,不过出题人想难为我们蒟蒻我们也做不上哈
2017-07-02
Manacher算法详解