题目链接

链接

翻译

让你选择字符串 \(s\) 的一个前缀和一个后缀(可以为空), 然后拼成一个字符串。

要求这个字符串得是一个回文串,且这个字符串的长度不能超过原串 \(s\) 的前提下最长。

输出这个字符串, hard 版本,长度小于等于 \(10^6\)

题解

接上文

现在的问题相当于要求从头部开始的连续回文串长 还是 以最后一个字符结尾的回文串长。

分开两步做,开头连续的情况,则将 \(s\) 转化为 s+"#"+reverse(s) 其中 reverse 为翻转操作。

那么现在相当于要找一个最大的数字 \(X\) 使得 \(s[1..X] == s[len-X+1,len]\)

这不就是 \(KMP\)\(f\) 数组吗。。所以对新的 \(s\) 做一下 \(KMP\)\(f[len]\) 的值就是所求的 \(X\) 了。

然后把原始的 \(s\) 反向一下,再进行上述的井号拼接操作,求 \(f\) 同样可以得到一个 \(X'\)

比较一下 \(X\)\(X'\) 谁大,就说明头部或尾部的回文串比较长,对应输出就好。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6;

int T;
string s,pres,afters;
int f[2*N+10];

int main(){
   // freopen("C://1.cppSourceProgram//rush.txt","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> T;
    while (T--){
        cin >> s;
        int l = 0,r = s.length()-1;
        while (l<=r && s[l] == s[r]){
            l++;r--;
        }
        if (l > r){
            cout << s << endl;
            continue;
        }
        pres ="";
        afters ="";
        if (0<l){
            pres = s.substr(0,l);
        }
        if (r+1<(int)s.length()){
            afters = s.substr(r+1);
        }
        if (l<r+1){
            s = s.substr(l,r-l+1);
        }else{
            cout << s << endl;
            continue;
        }
        string copyRawS = s;

        reverse(s.begin(),s.end());
        s = copyRawS + "#" + s;
        int len = s.length();

        //开始做 KMP
        f[0] = 0;f[1] = 0;
        int j;
        for (int i = 1;i < len; i++){
            j = f[i];
            while (j > 0 && s[i] != s[j]){
                j = f[j];
            }
            f[i+1] = j + (s[i]==s[j]?1:0);
        }
        //KMP 中 f[i+1]存的是以 i 这个字符结尾的后缀和字符串的前缀的最长公共前缀。
        //step1 求出前后最长公共前缀1

        int ma1 = f[len];

        //然后求从最后一个字符开始的回文串
        //step 2: 先得到对应的井号形式字符串
        s = copyRawS;
        reverse(s.begin(),s.end());
        s = s + "#" + copyRawS;

        //step 3: 同样求出f数组
        f[0] = 0;f[1] = 0;
        for (int i = 1;i < len; i++){
            j = f[i];
            while (j > 0 && s[i]!=s[j]){
                j = f[j];
            }
            f[i+1] = j + (s[i]==s[j]?1:0);
        }

        //step 4: 求出前后最长公共前缀2
        int ma2 = f[len];

        //step 5: 判断从前面开始比较长 还是从后面开始比较长, 得到中间部分的回文串。
        string midAns;

        //如果中间没有回文
        if (ma1 == 0 && ma1 == ma2){
            midAns = "";
        }else
        //前面比较长
        if (ma1 > ma2){
            midAns = copyRawS.substr(0,ma1);
        }
        //后面比较长
        else{
            int lenRaw = copyRawS.length();
            midAns = copyRawS.substr(lenRaw-ma2,ma2);
        }

        //step 6 输出三个部分答案
        cout << (pres+midAns+afters) << endl;
    }
    return 0;
}