题目链接
翻译
让你选择字符串 \(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;
}