D1. Prefix-Suffix Palindrome (Easy version)
题意: 给一个字符串 s,求一个字符串 t,t 由 s 的某个前缀以及某个后缀拼接而成,且 t 是回文串,长度不能超过 s。输出最长的 t
思路: 可以证明 :当
s
=
“
a
b
c
x
x
c
b
a
w
u
v
c
b
a
”
s=“abcxxcbawuvcba”
s=“abcxxcbawuvcba”,最终答案一定包含首部的
a
b
c
abc
abc 以及尾部的
c
b
a
cba
cba 两部分。因为此时的最大回文串有两种选法:1、全选前缀
a
b
c
x
x
c
b
a
abcxxcba
abcxxcba ;2、选前缀
a
b
c
x
x
abcxx
abcxx,与后缀
c
b
a
cba
cba。两种选法得到的答案是相同的,所以我们干脆就都选首部的
a
b
c
abc
abc与尾部的
c
b
a
cba
cba,然后在剩下的中间的字符串中找个最长的前后缀的回文子串,选较长的作为答案即可;
然后easy版的显然直接暴力搞就好了;
#include<bits/stdc++.h>
using namespace std;
string s;
bool check(int l,int r){ //判断是否为回文
while(l<=r) {
if(s[l]==s[r]) l++,r--;
else return false;
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--){
cin>>s;
string ans;
int i=0,j=s.size()-1;
while(i<=j){ //先找到首尾回文的最大位置,截取中间的字符串
if(s[i]==s[j]) i++,j--;
else break;
}
if(i>j) { //整个串是回文的
cout<<s<<"\n";continue;
}
int l=i,r=j; //前缀最大回文子串
while(r>=i){
if(check(i,r)) break;
else r--;
}
while(l<=j){ //后缀最大回文子串
if(check(l,j)) break;
else l++;
}
string ls = "",rs = "";
if(i!=0){ //防止首尾不回文
ls = s.substr(0,i);
rs = ls;
reverse(rs.begin(),rs.end());
}
if(r-i>=j-l) ans = ls + s.substr(i,r-i+1) + rs; //二者取最大拼接
else ans = ls + s.substr(l,j-l+1) + rs;
cout<<ans<<"\n";
}
return 0;
}
D2. Prefix-Suffix Palindrome (Hard version)
然后数据范围变大后,我们可以利用马拉车算法优化寻找最长的子串的时间,其他的代码不变;
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
namespace Manacher{
vector<int> ans, str, lef;
int build(const string &s){
// 初始化
int n = s.length(), m = (n + 1) << 1, ret = 0;
str.resize(m + 1); ans.resize(m + 1); lef.resize(m + 1);
// $ - 串开始标记, @ - 串结束标记,# - 将字母间隔开。注意这里的字符必须是字符串内不存在的
str[0] = '$'; str[m] = '@'; str[1] = '#'; ans[1] = 1;
for (int i = 1; i <= n; i++)
str[i << 1] = s[i - 1], str[i << 1 | 1] = '#';
// Manacher 操作。r, p 分别维护当前最靠右最长回文串的右边界与中心
ans[1] = 1;
for (int r = 0, p = 0, i = 2; i < m; ++i){
if (r > i) ans[i] = min(r - i, ans[p * 2 - i]); else ans[i] = 1; // Manacher 核心操作
while(str[i - ans[i]] == str[i + ans[i]]) ++ans[i]; // 暴力向外扩展
if (i + ans[i] > r) r = i + ans[i], p = i; // 尝试更新 r, p
ret = max(ret, ans[i] - 1); // 更新答案
}
// 计算维护以每个位置为起点的最长回文串
for (int i = 0; i <= m; i++) lef[i] = 0;
for (int i = 2; i < m; i++) if (lef[i - ans[i] + 1] < i + 1) lef[i - ans[i] + 1] = i + 1;
for (int i = 1; i <= m; i++) if (lef[i] < lef[i - 1]) lef[i] = lef[i - 1];
return ret;
}
int mid(int x, bool odd){ //返回以x为中心的最长回文子串的长度,odd为x的奇偶
if (odd) return ans[(x + 1) << 1] - 1;
return ans[(x + 1) << 1 | 1] - 1;
}
int left(int x){ return lef[(x + 1) << 1] - ((x+1) << 1); } //返回原串中以x为起点的最长回文子串长度
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--){
string s;
cin>>s;
int i=0,j=s.size()-1;
while(i<=j){
if(s[i]==s[j]) i++,j--;
else break;
}
if(i>j) {
cout<<s<<"\n";continue;
}
string ls = "",rs = "";
if(i!=0){
ls = s.substr(0,i);
rs = ls;
reverse(rs.begin(),rs.end());
}
string t = s.substr(i,j-i+1); //截取中间串
Manacher::build(t);
int l = Manacher::left(0); //前缀最长长度
reverse(t.begin(),t.end());
Manacher::build(t);
int r = Manacher::left(0); //后缀最长长度
string ans;
if(l>=r) ans = ls + s.substr(i,l) + rs;
else ans = ls + s.substr(j-r+1,r) + rs;
cout<<ans<<"\n";
}
return 0;
}