最长回文子串的查找

刚刚学习了manacher算法,感觉还是很优秀的,思路也很简单。具体介绍可以去http://www.felix021.com/blog/read.php?2040看。时间复杂度为 O(n)
然后后缀数组的做法,在罗穗骞大神的论文里也写得很清楚了,即把原串反着放回到前一个的后面(中间用一个特殊符号分割)(同样的,用特殊符号处理成长度为奇数的串)。然后再依次询问suffix(i) 与 suffix(n*2-1-i)(其中下标从0开始)的LCP,最大的LCP-1即为答案。若用线性时间算法(如DC3、KS03等等),并用 O(n) 预处理的RMQ,则时间复杂度为 O(n)
然后还有一种很常见的做法,即用Hash来判断。首先,正向求一边hash值,反向求一边hash值。然后二分答案,用Hash来判断是否是回文,时间复杂度 O(nlogn)
给出三种的代码。
Manacher:

#include<cstdio>
#include<cstring>
#define MAXN 2000005
char s[MAXN], s2[MAXN<<1];
int n, m, p[MAXN<<1];
inline int Min(int a, int b) { return a < b ? a : b; }
inline int Max(int a, int b) { return a > b ? a : b; }
int main() {
    scanf("%s", s);
    s2[0] = '@', s2[1] = '$';
    for(n = 0, m = 2; s[n]; ++ n)
        s2[m ++] = s[n], s2[m ++] = '$';
    int mp = 0, mx = 0; p[0] = 1;    //mp保存了之前能覆盖得最远的下标, mx保存答案。
    for(int i = 1; i <= m; ++ i) {
        if(mp + p[mp] > i) p[i] = Min(p[mp*2-i], p[mp] + mp - i);
        else p[i] = 1;
        while(s2[i + p[i]] == s2[i - p[i]]) ++ p[i];
        if(p[i] + i > p[mp] + mp) mp = i;
        mx = Max(mx, p[i]);
    }
    printf("%d\n", mx - 1);   //因为串是翻了倍的,所以不是2*mx-1.
}

后缀数组:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define MAXN 50005
#define REP(i, s, n) for(i = s; i < n; ++ i)
#define RREP(i, s, n) for(i = n-1; i >= s; -- i)
int ht[MAXN], ac[MAXN], ak[MAXN];
int sa[MAXN], c[MAXN], rk[MAXN];
int n, m, log2[MAXN], f[15][MAXN];
char s[MAXN];
inline bool cmp(int *x, int s, int t, int l) {
    return x[s] == x[t] && x[s+l] == x[t+l];
}
inline int Max(int a, int b) {return a > b ? a : b;}
inline int Min(int a, int b) {return a < b ? a : b;}
inline void Build() {
    int i, j, *x = ac, *y = ak, *t, p=0;
    REP(i, 0, m) c[i] = 0;
    REP(i, 0, n) ++ c[x[i] = s[i]];
    REP(i, 1, m) c[i] += c[i-1];
    RREP(i,0, n) sa[-- c[x[i]]] = i;
    for(j = 1; p < n; j <<= 1, m = p) {
        p = 0;
        REP(i,n-j,n) y[p ++] = i;
        REP(i, 0, n) if(sa[i] >= j) y[p ++] = sa[i] - j;
        REP(i, 0, m) c[i] = 0;
        REP(i, 0, n) ++ c[x[i]];
        REP(i, 1, m) c[i] += c[i-1];
        RREP(i,0, n) sa[-- c[x[y[i]]]] = y[i];
        for(t = y, y = x, x = t, p = 1, x[sa[0]] = 0, i = 1; i < n; ++ i)
            x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? (p-1) : p++;
    }
}
inline void CalcHt() {
    int i, j, k = 0;
    REP(i, 1, n) rk[sa[i]] = i;
    for(i = 0; i < n; ht[rk[i++]] = k)
        for(k?--k:0, j = sa[rk[i]-1]; s[i+k] == s[j+k]; ++ k);
}
char ch;
inline void GET(int &n) {
    n = 0; do ch = getchar(); while('0' > ch || ch > '9');
    while('0' <= ch && ch <= '9') {n=n*10+ch-'0';ch=getchar();}
}
inline int Q(int l, int r) {
    int t = log2[r - l + 1];
    return min(f[t][l], f[t][r - (1<<t) + 1]);
}
int spos, ans;
void update(int a, int b)
{
    if (b > ans) spos = a, ans = b;
    else if (b==ans && a<spos) spos = a, ans = b;
}
int main () {
    scanf("%s", s);
    n = strlen(s); m = 200;
    for(int i = 0; i < n; ++ i) s[n*2-i] = s[i];
    s[n] = '$'; n = n*2 + 2;
    for(int i = 1, x = -1; i < n; ++ i)
        log2[i] = (i == (i&-i)) ? ++ x : x;
    Build(); CalcHt();
    //下面是制作ST表
    for(int i = 1; i < n; ++ i)
        f[0][i] = ht[i];
    for(int j = 1; j <= 12; ++ j)
        for (int i = 1; i < n; ++i)
            if(i+(1<<(j-1)) < n) f[j][i] = min(f[j-1][i], f[j-1][i+(1<<(j-1))]);
    for(int i = 0, j = n-1, t1, t2, cur; i<n/2-1; ++ i, -- j) {
        t1 = rk[i + 1], t2 = rk[j];
        if (t1 > t2) swap(t1, t2);
        cur = Q(t1+1, t2);
        update(i - cur, 2*cur + 1);
    }
    for(int i = 1, j = n-2, t1, t2, cur; i<n/2-1; ++ i, -- j) {
        t1 = rk[i], t2 = rk[j];
        if (t1 > t2) swap(t1, t2);
        cur = Q(t1+1, t2);
        update(i - cur, 2*cur);
    }
    for (int i = spos; i<spos+ans; ++i) putchar(s[i]);//这是输出原串
}

二分+Hash:

#include<cstdio>
#include<cstring>
#define MAXN 4000005
#define LL long long unsigned
char a[MAXN], s[MAXN];
int n, m;
LL h1[MAXN], h2[MAXN], p[MAXN];
inline LL HashValue(int s, int t, bool f) {
    return f ? h1[t] - h1[s-1]*p[t-s+1] : h2[s] - h2[t+1]*p[t-s+1];
}
int main() {
    scanf("%s", a+1); p[0] = 1;
    s[1] = '@';
    for(n = 1, m = 2; a[n]; ++ n) {
        s[m ++] = a[n]; s[m ++] = '@';
    }
    for(n = 1; s[n]; ++ n)
        h1[n] = h1[n-1] * 251 + s[n], p[n] = p[n-1]*251;
    -- n;
    for(int i = n; i > 0; -- i) h2[i] = h2[i+1] * 251 + s[i];
    int l = 1, r = (n+1)/2, mid, ans = 0;
    while(l <= r) {
        mid = (l + r) >> 1;
        for(int i = mid+1; i <= n - mid; ++ i)
            if(HashValue(i-mid, i, 1) == HashValue(i, i+mid, 0)) {
                ans = mid, l = mid+1;
                break;
            }
        if(ans != mid) r = mid - 1;
    }
    printf("%d\n", ans);
}

你可能感兴趣的:(最长回文子串的查找)