题目大意:
已知长度为n的字符串str,我们从中抽出a,b。其中满足:
a是字符串的前缀,b是字符串的后缀。a+b是回文串。
n<=1e5
解题思路:
首先它是回文串,所以我们有一个观察,答案必定是这样构成的:
在这里不失一般性,我们假设后缀是比较长的。
同时我们发现,黑色部分的回文一定是贪心取最长的,不存在取短一点更优的情况。所以,在这里我们就变为了怎么求最长的红色的回文串。这里需要用到kmp算法。我们在原字符串中去掉黑色回文串后得到last.
然后构造新字符串:last + '$' + reverse(last),其中$是肯定不在last的一个字符。reverse代表字符串反转。
跑kmp得出的长度就是最佳前缀回文串的长度。要求最佳后缀回文串的话,只需要把last反转即可。
为什么可以这样做呢?因为kmp算法是可以O(N)得到在这个字符串中既是前缀也是后缀的最长的字符串。
加入这个特殊字符是为了防止越界,得到比原串更长的回文。至于为什么需要reverse,因为回文本质上就是正着读和反着读一样,所以reverse之后我们利用kmp找最长的既是前缀也是后缀这个特性,我们就能把回文找出来,可以在纸上画一下就知道了。
#include <bits/stdc++.h>
using namespace std;
const int M = (int)(2e6 + 239);
int pref[M];
string solve_palindrome(const string& s)
{
string a = s;
reverse(a.begin(), a.end());
a = s + "#" + a;
int c = 0;
for (int i = 1; i < (int)a.size(); i++)
{
while (c != 0 && a[c] != a[i])
c = pref[c - 1];
if (a[c] == a[i])
c++;
pref[i] = c;
}
return s.substr(0, c);
}
int main(){
int cas;cin>>cas;
while(cas--){
string str;cin>>str;
int n=str.size();
int l=0,r=n-1;
while(l<r && str[l]==str[r])l++,r--;
string ansmid("");
if(r-l+1>0){
string s=str.substr(l,(r-l+1));
ansmid=solve_palindrome(s);
reverse(s.begin(),s.end());
if(solve_palindrome(s).size()>ansmid.size())ansmid=solve_palindrome(s);
}
string ans1=str.substr(0,l);
string ans2=ans1;
reverse(ans2.begin(),ans2.end());
cout<<ans1<<ansmid<<ans2<<endl;
}
return 0;
}