Manacher算法详解及模板

算法详解

Manacher算法能够在线性的时间内求解最长回文子串,可以说实现的非常巧妙,他的基本思想是回文子串从中间向两边扩展一定是相同的,那么首先我们要对字符串进行一定的预处理:

比如:字符串:aaaa            aba

那么他们长度分别为偶数和奇数,因为偶数的话,我们找不到一个中点使得两边相同,那么我们在每两个字符之间添加一个不会出现的字符,使得他们全部变成奇数个

#a#a#a#a#           #a#b#a#      这样就一定可以找到一个最长回文串的中点了

我们令p[i],表示以i点为中心,可以拓展到最右边的长度,maxlen表示:可以拓展的最远距离(遍历过所有点中,可以拓展的最长距离)

现在我们要从头开始遍历字符串,来计算p[i],用p[i]来不断更新maxlen,那么因为我们添加了'#'的原因,我们最终的答案就是maxlen-1

假设我们当前遍历的位置为k+i,并且k前面所有点中i为拓展到的最远距离,即maxlen = p[i]

后面的情况分为两种:

1)   当k+i > maxlen   ,我们找不到与这个点相对应的点,那么就初始化p[k+i] = 1,然后从这个点向两边遍历求解最长回文字串:

while(s[k + p[k+i]] == s[k - s[k+i]) p[k+i]++;

2) 当 k+i < maxlen 时

①当和与之对应的点i-k回文串左端大于i的左端点时


这样的话,我们的p[i+k] 的长度一定为i+k到i右端点的长度,即:p[i+k] = p[i] - k,因为如果更长的话,那么i的长度一定不止现在的长度,还是可以向右延伸的

当和与之对应的点i-k回文串左端大于i的左端点时


这时p[i+k] = p[i-k]

当和与之对应的点i-k回文串左端等于i的左端点时


这时p[i+k]的要大于等于p[i-k],然后我们需要继续判断while(s[i+k+p[i+k]] == s[i+k - p[i-k]] ) p[i+k] ++;

我们总结可以发现:

p[i+k] = min(p[i] - k , p[i - k]); 
while(s[i+k+p[i+k]] == s[i+k - p[i-k]] ) p[i+k] ++;

那么这样遍历到字符串结尾就可以求解出最终的答案

栗子及模板

HDU 3068

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;
const int N = 110000 + 10;
int main()
{
    char s[N*2];
    int p[N*2];
    while(~scanf("%s",s))
    {
        int len = strlen(s);
        for(int i = len;i >= 0;i --)
        {
            s[i+i+2] = s[i];
            s[i+i+1] = '#';
        }
        s[0] = '*';
        int maxlen = 0,id = 0;
        for(int i = 2;i < len*2+1 ;i ++)
        {
            if(p[id]+id > i) p[i] = min(p[id*2-i] , p[id] + id - i);
            else p[i] = 1;
            while(s[i + p[i]] == s[i - p[i]]) p[i] ++;
            if(p[id]+id < p[i]+i) id = i;
            if(maxlen < p[i]) maxlen = p[i];
        }
        printf("%d\n",maxlen-1);
    }
    return 0;
}

参考博客

https://blog.csdn.net/xingyeyongheng/article/details/9310555

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/80241231
今日推荐