题目描述

 

- 基础方法:枚举子串,判断是否为回文串。

- 改进:枚举中间位置,向两侧拓展。

- 再改进:利用以前的信息,使得不用每个新位置都从长度1开始拓展。

- 优化:将字符串预处理为奇数长度以避免考虑条件分支。

- 再优化:开头加入特殊字符避免考虑边界。

 

Manacher 算法:

id 是中心点,mx 是其以id为中心的最长回文子串的边界。P[i] 表示以 i 为中心的最长回文子串的折半长度,如 P[id] = (mx - id)。

只要 i < mx, 以 i 为中心的回文子串就可以不必从长度1开始找,而从min{P[j], mx - i}开始(其中j为i关于id的对称点)。

当mx可以向右前进时(i + P[i] > mx),改变id和mx的值。

其实看看下面两个图就可以懂了,P[j]的存在可以使得P[i]不用从1开始算。

再简单说明一下为什么这个算法的时间复杂度是线性的。

对于第一副图的情况(P[j] < mx - i),其实是不用继续进行比较 str[i + P[i]] == str[i - P[i]] 的,因为前面在 j 处已经比过了。

真正需要比较的是第二种情况,而第二种情况下的比较必然会使 mx 右移(或着不动)。

综合以上说明,每次的比较都使得 mx 右移,在 mx 左侧的不需要再比较,于是得出算法时间复杂度是线性的。

 

具体实现的代码如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAX_SIZE 1000000
char str[2 * MAX_SIZE + 10];
int p[2 * MAX_SIZE + 10];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%s", str);
        int n = strlen(str);
        for (int i = n - 1; i >= 0; i--) {
            str[2*i+2] = str[i];
            str[2*i+3] = '#';
        }
        str[0] = '$';
        str[1] = '#';
        memset(p, 0, sizeof(p));
        int id = 0, mx = 0;
        int res = 0;
        for (int i = 1; i < 2*n+4; i++) {
            p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
            while (str[i + p[i]] == str[i - p[i]]) {
                p[i]++;
            }
            res = max(res, p[i]);
            if (i + p[i] > mx)
            {
                mx = i + p[i];
                id = i;
            }
        }
        printf("%d\n", res - 1);
    }
    return 0;
}

 

吐槽:p数组大小没乘2,但是hiho居然报wa。白找了半天错。

 

Ref:http://taop.marchtea.com/01.05.html

 posted on 2015-04-19 22:36  xblade  阅读(212)  评论(0)    收藏  举报