Manacher算法总结

一、用途:

给一个字符串,求它的最长回文子串;比如:
s = "abbacbca",最长回文子串为 "acbca",长度为 5 5
如果用暴力的算法,枚举对称轴,向两边延伸;复杂度高达 O(n2) O ( n 2 ) !
有个叫 Manacher 的人发明了一种算法,可以 O(n) O ( n ) 的求出最长回文子串,就叫 Manacher 算法(俗称 马拉车算法);

二、算法详情:
2.1 预处理:

回文串分为 奇回文串(如 "acbca") 和 偶回文串(如 "abba"),处理的时候判奇偶是一件很麻烦的事,所以用一个小技巧对原串进行预处理:
在头尾以及两两字符中间都插入一个无关字符 (没有出现在这个串中的字符);
举个例子:"abcd" 填充之后 变为 "#a#b#c#d#";只要是无关字符都可以用来填充;
偶回文串 "abba" 处理后变为 "#a#b#b#a#",奇回文串 "acbca" 处理后变为 "#a#c#b#c#a#",长度都变成了奇数

2.2 p[] 的定义:

首先定义一个 p p 数组:
p[i] p [ i ] 表示以 i i 为中心的回文串的最大回文半径。
比如:

i01234567891011121314151617
New_s@#a#b#b#a#c#b#c#a#
p[i]12125212121612121

可以看出, max(p[i]1) m a x ( p [ i ] − 1 ) 就是原串的最长回文子串的长度;

2.3 求解p[]

假设我们正在求 p[i] p [ i ] ,即 p[1,,i1] p [ 1 , ⋯ , i − 1 ] 都已求出;
新增两个变量 Mr 和 Mid,定义如下:
Mr : 中心在 i i 之前的所有回文子串,所能延伸至的最右端的位置;
Mid : 右端延伸至 Mr 处的回文子串的中心位置
Mid + p[Mid] = Mr

假设变量的相对位置如图:
(1) if (i < Mr)
即以 Mid 为中心的回文串为黑色的那段覆盖了红色的两段,根据回文串的性质,有 以 j 为中心 的回文串 和 以 i 为中心 的回文串相等,即图中红色两段相等;
既然这样,就不用以 i i 为中心向两边延伸了,直接 p[i] = p[j] ,加速查找;
这里写图片描述
(2) else
i i <script type="math/tex" id="MathJax-Element-5811">i</script> 在 Mr 右边,这种情况,只能老老实实向两边延伸了;
这里写图片描述

2.4 参考代码:
/**********************************************
 *Author*        :XzzF
 *Created Time*  : 2018/4/12 16:42:30
 *Ended  Time*  : 2018/4/12 16:57:48
*********************************************/

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 110005;

char s[MaxN + 5];
char New_s[2 * MaxN + 5];  //填充字符之后的串
int p[2 * MaxN + 5];  //p[i]表示以i为中心的最长回文串半径

int Get_New() {
    int len = strlen(s);
    New_s[0] = '@'; New_s[1] = '#';
    int L = 1;
    for(int i = 0; i < len; i++) {
        New_s[++L] = s[i];
        New_s[++L] = '#';
    }
    New_s[L + 1] = '\0';   //不要忘了
    return L;
}

int Manacher() {
    int len = Get_New();
    int Max_len = 0;
    int Mr = 0;       //遍历过的所有回文串中,所能到达的最右端的位置
    int Mid = 0;       //能到达最右端位置的回文串的中心位置
    for(int i = 1; i <= len; i++) {
        if(i < Mr) 
            p[i] = min(p[2 * Mid - i], Mr - i);
        else p[i] = 1;

        //不需边界判断,因为左有'@',右有'\0'
        while(New_s[i - p[i]] == New_s[i + p[i]])
            p[i]++;

        //更新Mr
        if(Mr < i + p[i]) {
            Mid = i;
            Mr = i + p[i];
        }
        Max_len = max(Max_len, p[i] - 1);
    }
    return Max_len;
}

int main()
{
    while(scanf("%s", s) != EOF)
    {
        printf("%d\n", Manacher());
    }
    return 0;
}
算法复杂度分析:

这里写图片描述
这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值