Manacher学习笔记

目录

Manacher算法 可在 (O(n))的时间内求出一个字符串以每个位置为中心的最长回文子串。

原理:根据之前预处理出的回文串长度求得新的回文串长度

我们可以通过在字符中加上'#'来避免长度为偶数回文串没有中心的问题

原串 = "abcd"; 变为
新串 = "#a#b#c#d#";
原串中长度为奇数和偶数的回文串的长度均变为奇数,且原串中回文串的长度为新串回文串半径减一。

设置两个状态 $max $ 和 (p) , (p) 表示当前已找到的回文串中,向右延伸最远的中心位置,$max $ 表示其右端点。

(r(i)) 表示(新串中)第 (i) 个位置的回文半径(回文串长度的一半,包括第 (i) 个字符)按从左到右的顺序求解,枚举到第 (i) 个字符时,分三种情况考虑:

( j)( i) 关于 (p) 的对称点,即 (j = 2p - i)

img

(max < i​),即向右延伸最远的回文子串(黑色)没有覆盖 (i​),此时只有 (r(i) geq 1​)

img

(max geq i)(max - i geq r(j)),即向右延伸最远的回文子串(黑色)覆盖了 (i),并且以 jj 为中心的最长回文子串完全与以 (i) 为中心的最长回文子串对称(蓝色),此时一定有 (r(i) = r(j)),即 (r(i) geq r(j))

img

(max geq i)(max - i geq r(j)),即向右延伸最远的回文子串(黑色)覆盖了 (i),但没有覆盖以 jj 为中心的最长回文子串的对称位置串,所以 (r(i)) 只能取被覆盖的(黄色)一部分,即 (r(i) geq max - i)

code(伪)

int len;
void prepare(){
    len = 0;
    s2[++len] = '%';
    for(int i = 0; i < s.size(); i++){
        s2[++len] = '#';
        s2[++len] = s[i];
    }
    s2[++len] = '#';
}
void manacher(){
    int mid = 0, rb = 0;
    //此处的rb是 真实值加一
    //回文串为(lb, rb) (lb, mid] = [mid, rb) 所以计算半径直接rb - mid;
    //因为每次扩展x相当于扩展rb, rb最多扩展n次, 所以O(N);
    for(int i = 1; i <= len; i++) {
        int x;
        if(rb < i) x = 1;
        else x = min(p[2*mid - i], rb - i);
        
        while(s2[i+x] == s2[i-x]) x++;
        
        if(i > rb) mid = i, rb = i+x;
        p[i] = x;
    }
}

ans = max(p[]) - 1; 
//原串回文串长度等于新串回文半径减一.
原文地址:https://www.cnblogs.com/Eroad/p/9373195.html