首页 > 编程语言 >Manacher算法

Manacher算法

时间:2023-01-24 22:34:54浏览次数:55  
标签:Manacher 位置 mid times 半径 算法 字符串 回文

文章默认给定字符串中只会出现小写英文字母

介绍

通过已经学习了的 字符串哈希 ,我们可以用 \(O(n\log n)\) 的时间复杂度求解一个串中的最长回文子串了,那么我们思考一下是否用字符串哈希在线性的时间内完成这个问题呢?

当然可以!但是具体做法我们不会在此介绍,感兴趣可以看 OI Wiki 中的做法。

在求解回文串问题的时候,我们会更常使用到一个算法,它能在线性时间内求出该串中全部位置的最长回文半径,这个算法称为 \(Manacher\) 算法。

预处理

在求解一个位置的回文半径时,显然需要该串的长度为奇数,我们可以直接选取该位置作为中心,然后向两侧扩展即可。例如:

字符串 a b a
索引 1 2 3
回文半径 1 2 1

这个串的每个位置的回文半径显然是不难求得的,但是如果是这种情况:

字符串 a b b a
索引 1 2 3 4
回文半径 1 1 1 1

如果按照所求出的解,该串应该不含长度大于 \(1\) 的回文子串,但显然这与事实相悖,因此为了得到一个正确的解,我们需要对该字符串进行加工处理。

具体处理的方法是在原有串的字符之间加入一些不属于该串的字符,以串 \(abba\) 举例,我们可以将其变为 \(|a|b|b|a|\) ,串的长度从原来的 \(n\) 变为了现在的 \(2n+1\) 在处理之后我们可以再观察一下每个位置的回文半径:

字符串 | a | b | b | b |
索引 1 2 3 4 5 6 7 8 9
回文半径 1 2 1 2 5 2 1 2 1

虽然处理后的字符串可以全部转换为奇数长度的情况求解回文半径,但是处理后的回文半径显然与未处理的字符串有些差别,通过简单的观察,我们可以发现在进行字符匹配的时候, \('|'\) 只会和 \('|'\) 匹配,同样的字母也只会和字母匹配。

下面记位置 \(i\) 的回文半径为 \(p_i\)

  • 以 \('|'\) 为中心的位置对应的 \(p_i\) 一定为奇数。因为匹配的起点是 \('|'\) ,且终点也为 \('|'\) 。除了中心以外,字母和 \('|'\) 都是成对出现的。

  • 以字母为中心的位置对应的 \(p_i\) 一定为偶数。因为匹配的起点是字母 ,且终点为 \('|'\) 。全部字母和 \('|'\) 都是成对出现的。

不难发现,在处理完字符串中全部位置所对应的 \(p_i\) 后,\(p_i-1\) 就是对应原字符串中最长的回文串长度

优化

在知道 \(p\) 数组的含义以后,我们要做的就是以 \(O(n)\) 的时间复杂度来求解每一个位置所对应的 \(p_i\) 。

在实现该算法之前,我们需要先定义两个变量 \(mid\) 和 \(R\) ,且初值可以均赋为 \(1\) ,其意义和作用会在下面提到。

计算 \(p_i\) 的过程是自左向右的,假设我们当前在求第 \(i\) 个位置的 \(p_i\) ,那么 \(p_1,p_2...p_{i-1}\) 是已经被计算完成了的。在此时我们需要动态维护一个区间使得 \(R\) 最大,其中:

\[\begin{cases} L = mid - p_{mid} +1, \quad(mid < i) \\ R = mid + p_{mid} - 1, \quad(mid<i) \end{cases} \]

\(mid\) 是小于 \(i\) 的一个位置,我们需要在完成一个位置的 \(p_i\) 的统计后及时更新 \(R\) 的值。

那么我们通过图片来理解一下

image

此刻有很多种情况需要讨论

  • \(i>R\) 时,令 \(p_i=1\) ,并暴力向两侧扩展

    image

因为此时我们统计出来的这个区间与该位置没有关系,只能暴力计算

  • 当 \(i\leq R\) 时,首先需要找到点 \(i\) 关于 \(mid\) 的对称点 \(i^{'}\) \((i^{'}=mid\times 2 - i)\),然后进一步分类讨论

    • 当 \(p_{i^{'}}\) 所对应的回文区间不包含 \(L\) 时

      image

      \[黄色直线代表以\;i\;为中心的回文区间,对于 i^{'}同理 \]

      由于以 \(i^{'}\) 为中心的回文串与以 \(i\) 为中心的回文串也关于 \(mid\) 对称,因此不难得出 \(p_i=p_{i^{'}}\)

    • 当 \(p_{i^{'}}\) 所对应的回文区间包含 \(L\) 时

    image

    \[黄色直线代表以\;i\;为中心的回文区间在[L,R]内的部分,红色直线代表\;i^{'}\;的回文区间超出[L,R]的部分 \]

    可以通过上一条性质,发现黄色部分是可以继承的,但是红色部分是不确定的,因此需要再暴力匹配。即 \(p_i=p_{R-i+1}\) 后暴力扩展

至此全部情况已经讨论结束,但是当前情况未免过于臃肿,因此我们可以对这些情况进行化简。

  • 当 \(p_i\) 对应的回文区间不包含 \(L\) 时,则 \(L<i^{'}-p_{i^{'}}+1\) ,又有 \(L=2\times mid -R\) 和 \(i^{'} =mid\times 2 - i\) 则 \((2\times mid - R) < (2 \times mid - i) - p_{2 \times mid - i} +1\)

    化简式子可得 \(p_{2 \times mid - i} < R - i + 1\) ,即当 \(p_{2 \times mid -i}<R-i+1\) 时, 令 \(p_i=p_{2\times mid-i}\)

  • 当 \(p_i\) 对应的回文区间包含 \(L\) 时,则 \(i^{'}-p_{i^{'}}+1\leq L\) ,又有 \(L=2\times mid-R\) 和 \(i^{'} =mid\times 2 - i\) ,则 \(2\times mid -i-p_{2\times mid-i}+1\leq 2\times mid-R\)

    化简式子可得 \(R-i+1\leq p_{2\times mid -i}\) ,即当 \(R-i+1\leq p_{2\times mid -i}\) 时,令 \(p_i=p_{R-i+1}\) 并继续暴力扩展

​ 通过上述两种情况的归纳可得,当 \(i\leq R\) 时,令 \(p_i=\min{(p_{2\times mid-i},R-i+1)}\) 并继续暴力扩展

代码实现

点击查看代码
struct manacher
{
    char s[N << 2];
    int n;
    int p[N << 2];
    void init(string &str)
    {
        n = str.size();
        s[0] = s[1] = '|';
        for (int i = 0; i < n; i++)
        {
            s[i * 2 + 2] = str[i];
            s[i * 2 + 3] = '|';
        }
        n += n + 2;
        s[n] = 0;
    }
    int work()
    {
        int mid = 1, mx = 1, res = -1;
        for (int i = 1; i < n; 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];
            }
            res = max(res, p[i] - 1);
        }
        return res;
    }
};

例题

标签:Manacher,位置,mid,times,半径,算法,字符串,回文
From: https://www.cnblogs.com/Jadebo1/p/17066461.html

相关文章

  • 2023牛客寒假算法基础集训营3 A-I+K
    A题解知识点:贪心。把所有正偶数除成奇数,即可。(人傻了没加\(x>0\)WA2时间复杂度\(O(n)\)空间复杂度\(O(1)\)代码#include<bits/stdc++.h>usingll=longlon......
  • Matlab复现RRT算法
    快速搜索随机树(Rapid-explorationRandomTree,RRT)算法是一种在完全己知的环境中通过随机采样扩展搜索的算法特点:RRT算法是概率完备的,如果规划时间足够长,如果确实存在一......
  • 梅森旋转算法
    梅森旋转算法(Mersennetwister)是一个伪随机数发生算法。由松本真和西村拓士在1997年开发,基于有限二进制字段上的矩阵线性递归。可以快速产生高质量的伪随机数,修正了古典随......
  • 机器学习算法-有监督学习的定义与模型
    有监督学习的定义与模型 机器学习的算法可以分为以下三类:有监督学习(SupervisedLearning):有预测目标Y,通过X预测Y无监督学习(UnsupervisedLearning):没有Y,只通过X......
  • 使用栅格地图复现Floyd算法
    Floyd算法适用于APSP(AllPairsShortestPaths,多源最短路径),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于Di......
  • 使用栅格地图复现迪杰斯特拉算法
    Dijkstra算法可以计算出在有权图中从某个起点出发到其他任何一点的最短路径长度算法思想:迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最......
  • 2023牛客寒假算法基础集训营1 个人题解(ACDHKL)
    A.WorldFinal?WorldCup!(I)题意:给10场比赛的点球输赢情况,奇数为A队点球,偶数为B队点球思路:用两个变量x,y来分别存A队当前赢的场次和B队当前赢的场次然后就就扫......
  • python入门学习笔记002--趣学Python算法--第2例兔子产子
    例题如下:有一对兔子,从出生后的第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子,假设所有的兔子都不死,问30个月内每个月的兔子总对数为多少?  个......
  • python入门学习笔记001--趣学Python算法--第一例抓交通肇事犯
    本人是python小白初学者,过年期间实在闲的无聊,偶尔翻到《趣学Python算法100例》这本书,浅浅阅读后感觉写的很不错。本系列案例均取自该书,只分享题目和自己的编的代码,问题分析......
  • 代码随想录算法训练营day10 | leetcode 232.用栈实现队列 225. 用队列实现栈
    基础知识使用ArrayDeque实现栈和队列stackpushpoppeekisEmpty()size()queueofferpollpeekisEmpty()size()LeetCode232.用栈实现队列分析1.0队列先进先出......