题解-最长回文子串


刚学完后缀数组,用这道题来练练\(SA\)

(〇)题目描述

题目右转:URAL 1297

题意如题目,即给出一个字符串 \(S\) ,求 \(S\) 的最长回文子串。(\(|S|\leqslant1000\))

(二)解题思路

既然刚学了后缀数组,自然使用后缀数组做啦。

但如何将问题转化为一个 \(SA\) 问题呢\(?\)

先来观察一下下面这个字符串的回文子串吧。\(RT,\)

可以发现,回文子串 \(baaaab\),是由两个部分组成(前半部分和后半部分)。前半部分是字符串 \(aabaaaab\) 的前缀的后缀,后半部分是原字符串的后缀的前缀,回文子串即为原字符串的前缀的后缀和原字符串的后缀的前缀两部分组成(可能读起来有点绕,需要认真理解)。

回文串有个重要的性质,即从字符串中心将字符串切开,后半部分刚好是反过来的前半部分(例如: \(abc\) 是反过来的 \(cba\) )。因此,若一个子串是回文的,那么其一定是由原字符串某个后缀的前缀和原字符串的某个前缀的后缀的公共部分组成的。

如果我们将字符串调转过来,在接到原字符串的后面,中间隔一个字典序最小的'板',这样前缀就会被调转过来,‘前缀的后缀’就会变为‘后缀的前缀’。问题就转化为了求某两个后缀的最长公共前缀,就可以用后缀数组求解了。

(三)解题方法

后缀数组+\(RMQ\)

时间复杂度:\(\Theta(n\log n)\)

(如果后缀数组用\(DC3\)求解的话,时间复杂度会降为\(\Theta(n)\))

首先,将字符串调转过来,连接到原字符串的后面。然后在两个字符串交界处插入一个字典序最小的字符,以隔开两个字符串。\(RT,\)

分两种情况讨论回文串1.长度为奇数的 2.长度为偶数的。

如果长度是奇数,就查询后缀\(i\)和后缀\(n-i\)。看图自行理解。(注意,\(n\)整个字符串的长度)

如果长度是偶数,就查询后缀\(i\)和后缀\(n-i+1\)。请看图自行理解。

至于查询任意两个后缀的最长前缀,其实是查询这两个后缀的排名之间的\(height\)数组的最小值。(原因在此不进行阐述),用\(RMQ\)经过\(\Theta(n)\)的预处理,即可\(\Theta(1)\)的出答案。

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005*2;
int rak[maxn], newRank[maxn], sa[maxn];
int sum[maxn], key2[maxn], height[maxn];
int rmq[20][maxn], Log2[maxn];
int n, m;
char str[maxn];
bool cmp(int a, int b, int l)
{
    if(rak[a] != rak[b]) return false;
    if((a+l > n) xor (b+l > n)) return false;
    if(a+l > n and b+l > n) return true;
    return rak[a+l] == rak[b+l];
}
void makesa()
{
    for(int i=1; i<=n; i++) sum[rak[i] = str[i]]++;
    for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
    for(int i=n; i>=1; i--) sa[sum[rak[i]]--] = i;
    for(int l=1; l<n; l<<=1)
    {
        int k = 0;
        for(int i=n-l+1; i<=n; i++) key2[++k] = i;
        for(int i=1; i<=n; i++) if(sa[i] > l) key2[++k] = sa[i]-l;
        for(int i=1; i<=m; i++) sum[i] = 0;
        for(int i=1; i<=n; i++) sum[rak[i]]++;
        for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
        for(int i=n; i>=1; i--) sa[sum[rak[key2[i]]]--] = key2[i];
        int rk = 1;
        newRank[sa[1]] = 1;
        for(int i=2; i<=n; i++)
        {
            if(cmp(sa[i-1], sa[i], l)) newRank[sa[i]] = rk;
            else newRank[sa[i]] = ++rk;
        }
        for(int i=1; i<=n; i++) rak[i] = newRank[i];
        if(rk == n) break;
    }
}
void getHeight()
{
    int k = 0;
    for(int i=1; i<=n; i++)
    {
        if(rak[i] == 1) continue;
        int j = sa[rak[i]-1];
        while(str[j+k] == str[i+k] and i+k <= n and j+k <= n) k++;
        height[rak[i]] = k;
        if(k) k--;
    }
}
void getRMQ()
{
    for(int i=1; i<=n; i++) rmq[0][i] = height[i];
    for(int l=1; l<=20; l++)
       for(int i=1; i<=n; i++)
       {
          int j = min(i+(1<<(l-1)), n);
          rmq[l][i] = min(rmq[l-1][i], rmq[l-1][j]);
       }
    for(int i=1; i<=n; i++)
    {
        if(i > (1<<Log2[i-1])<<1) Log2[i] = Log2[i-1]+1;
        else Log2[i] = Log2[i-1];
    }
}
int getmin(int l,int r)
{
    int len = Log2[r-l+1];
    return min(rmq[len][l], rmq[len][r-(1<<len)+1]);
}
int check(int l, int r)
{
    int a = rak[l],  b = rak[r];
    if(a > b) swap(a, b);
    a++;
    return getmin(a, b);
}
int main()
{
    scanf("%s", str+1);
    n = strlen(str+1);
    str[n+1] = '@';
    for(int i=1, j=2*n+1; i<=n; i++, j--) str[j] = str[i];
    n = n*2+1;
    m = max(n, 256);
    makesa();
    getHeight();
    getRMQ();
    int ans = 0, start = 0;
    for(int i=1; i<=n/2; i++)
    {
        int len = check(i, n-i+1);
        if(len*2-1 > ans) ans = len*2-1, start = i-len+1;
        len = check(i, n-i+2);
        if(len*2 > ans) ans = len*2, start = i-len;
    }
    for(int i=start, j = 1; j<=ans; j++, i++) printf("%c",str[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/GDOI2018/p/10296315.html