• 「Manacher算法」学习笔记


    觉得这篇文章写得特别劲,插图非常便于理解。

    目的:求字符串中的最长回文子串。


    算法思想

    考虑维护一个数组$r[i]$代表回文半径。回文半径的定义为:对于一个以$i$为回文中心的奇数回文子串,设其为闭区间$[L,R]$,则半径$r=R-i+1$。

    $Manacher$算法利用一个类似$DP$的方法来求解这个问题。考虑维护一个目前已经达到的最大的右边界$P$,此右边界对应的对称中心以及左边界分别为$pos$,$P'$。那么分类讨论:

    1. $i<P$

    此时我们可以找到$i$关于$pos$的对称点$j$。由于$[P',P]$是回文的,所以如果$j$的回文子串不超过边界,那么有$r[i]=r[j]$。

    如果$j$超过边界了,说明至少区间内的那一段是能够满足的。因此$r[i] geq P-i+1$。对于超出的部分,暴力比较(同时更新P)

    2. $i geq P$

    此时根据前面的来转移已经没有意义了。直接暴力。

    综上我们已经可以求解出所有的$r[i]$了。那么前面所说的回文中心一定要是奇数的长度。能不能把偶数转化为奇数?

    答案是在任意两个相邻字符之间插入特殊符号进行间隔(包括开头和结尾)。这样就一定是奇数了。易知对于这种情况,对称中心$i$对应的回文串的长度也就是$r[i]-1$。


    刚才是用数学角度在讨论问题,如果代码也按照分类讨论这个标准来实现未免有些冗长。考虑简化问题。

    既然$i geq P$和$j$出界的情况都需要暴力匹配,不如合并到一起?我们发现只要我们确定$r[i]$的最小取值,然后不停增大$r[i]$判断是否成立就好了。当$i geq P$时,由于未知,最小值固然是1. 而对于$j$出界,最小值固然是$P-i+1$。因此问题就很简单了


    $code$

    inline void Manacher(){
        int j=0,P=0,pos=0,p=0,N=0,n=strlen(t+1);
        for(int i = 1; i <= n; ++i){
            s[++N] = '$';
            s[++N] = t[i];
        }
        s[++N] = '$';
        for(int i = 1; i < N; ++i){
            if(P > i) r[i] = Min(r[pos-(i-pos)], P-i+1); else r[i] = 1;
            while(s[i-r[i]] == s[i+r[i]] && i-r[i]>=1 && i+r[i]<=N) ++r[i];
            if(i+r[i]-1 > P) P = i+r[i]-1, pos = i;
            ans = Max(ans, r[i]-1);
        }
    }
  • 相关阅读:
    004 连接查询
    003 常用函数说明
    003 限定查询
    002 基础查询
    001 基础数据表脚本
    001 redis的简介和重点
    006 表单组件
    005 基本表单
    004 表格元素
    谚语,
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/10046478.html
Copyright © 2020-2023  润新知