这是 Reach-Top 2385 题的题解,翻译自 Codeforces。

题目

因为被各种自己瞎倒腾都只能收获TLE,所以查了一些资料,了解到了这个名为Manacher的算法,忍不住就打算写篇博客记录一下。

概述 链接到标题

这是一个专门用来处理最长回文子串的算法,主要思想是:遍历每个串中的每个字符,且将其定为一个回文串的中心,并向两边扩展,以此寻找最长回文子串。但需要关注的是偶数个数的子串无法直接使用该法,所以在算法中我们先预处理一下字符串,使其的回文子串全部变成奇数个数的。

1.预处理 链接到标题

首先算法提供了一个巧妙的办法:在相邻两个字符间插入一个不会出现在原串中的字符串(一般为 # ),同时首尾也各加一个。

例如: example1

2.Len数组 链接到标题

简介 链接到标题

Len[i]用于表示以字符T[i]为中心的最长回文子串右边界到T[i]的长度,比如以T[i]为中心的最长回文子串是从T[l]到T[r],那么Len[i]=r-i+1。

于是对于上面给出的例子,我们可以得出Len数组为: example2

性质 链接到标题

Len[i]-1 = 该回文子串在原字符串S中的长度

证明:

已知字符串T中所有的回文子串长度都为奇数,那么对于以T[i]为中心的最长回文子串,其长度就是2*Len[i]-1。

根据预处理的方法可以知道,分隔符 # 的数量一定比其他字符的数量多1,所以该最长回文子串中有Len[i]个分隔符 # 和Len[i]-1个其他字符。

3.计算Len数组 链接到标题

有了上面的性质,题目变转化为了求所有的Len[i],然后找出最大值输出即可,下面介绍如何在线性时间复杂度内求出结果。

因为是从左往右计算,所以当我们计算Len[i]时,Len[j](0 <= j < i)就已经计算好了。

设P为已计算的最长回文子串的右边界中的最大值,Po为P所在的最长回文子串的中心,那么计算Len[i]就可以分成两种情况了:

第一种情况: i <= P 链接到标题

通过回文字符串的定义我们可以知道在Po的左边距离等于i-Po的地方找到一个j,这个j和i存在一定的关系。

Len[j] < P - i 链接到标题

这时的情况如下图:

example3

因为以T[i]、T[j]为中心的回文子串都在以T[Po]为中心的最长回文子串中,所以很显然此时Len[j]=Len[i]。

Len[j] >= P - i 链接到标题

此时的情况如下图:

example4

P的右侧是我们还没有匹配过的区域,所以要从P+1的位置开始进行对应匹配,直到发生失配,此时以Len[i]为中心的最长回文子串的右边界已经在P右侧了,所以P、Po都需要更新了。

第二种情况:i > P 链接到标题

在这种情况下以T[i]为中心的回文串还一点都没匹配过,只能老老实实匹配了,发生失配后也要更新P和Po。

example5

4.时空复杂度 链接到标题

时间复杂度 链接到标题

我们从上面的Len数组计算中可以知道算法只在没匹配过的地方进行匹配,而已经匹配过的地方是不需要重新匹配的,因此时间复杂度是线性的。

空间复杂度 链接到标题

显然这是将空间扩大了一倍,为O(2n).

5.代码 链接到标题

这里我在字符串T的最前面加了一个特殊字符 $ 来避免频繁更新P的时候导致数组越界。

#include<bits/stdc++.h>
using namespace std;
int main() {
    int n;
    string S;
    while(cin >> S) {
        string T = "$#";
        for(int i = 0; i < S.size(); i ++) {    //预处理字符串
            T += S[i];
            T += "#";
        }
        vector<int> Len(T.size(), 0);   //辅助数组Len
        int Po = 0, P = 0, start = 0, maxLen = 0;
        for(int i = 1; i < T.size(); i ++) {
            Len[i] = i < P ? min(Len[2 * Po - i], P - i) : 1;
            while(i + Len[i] < T.size() && i - Len[i] > 0 && T[i + Len[i]] == T[i - Len[i]]) {  //这里进行匹配,注意别让数组越界
                Len[i] ++;
            }
            if(i + Len[i] > P) {    //更新P和Po
                Po = i;
                P = i + Len[i];
            }
            if(Len[i] - 1 > maxLen) {
                start = (i - Len[i]) / 2;
                maxLen = Len[i] - 1;
            }
        }
        cout << maxLen << endl;
    }
    return 0;
}