洛谷P4555 [国家集训队] 最长双回文串
[国家集训队]最长双回文串
题目描述
顺序和逆序读起来完全一样的串叫做回文串。比如acbca
是回文串,而abc
不是(abc
的顺序为abc
,逆序为cba
,不相同)。
输入长度为\(n\)的串\(S\),求\(S\)的最长双回文子串\(T\),即可将\(T\)分为两部分\(X\),\(Y\),(\(|X|,|Y|≥1\))且\(X\)和\(Y\)都是回文串。
输入格式
一行由小写英文字母组成的字符串\(S\)。
输出格式
一行一个整数,表示最长双回文子串的长度。
样例 #1
样例输入 #1
baacaabbacabb
样例输出 #1
12
提示
【样例说明】
从第二个字符开始的字符串aacaabbacabb
可分为aacaa
与bbacabb
两部分,且两者都是回文串。
对于100%的数据,\(2≤|S|≤10^5\)
2018.12.10、2018.12.15 感谢 @Ycrpro 提供 hack 数据两组
SOLUTION
题意为求出 \(S\) 的一个子串 \(T\),并且 \(T\) 可以分为两个非空的回文串。
对于回文串,我们一般考虑 马拉车(manacher) 来解决,对于本题,考虑分为两个非空的回文串,那么间隔点一定是 # ,那么我们可以维护出每个位置的回文半径 \(p_i\) 的同时,可以维护以每个最长回文子串的左端点和右端点,然后分别记录出以他们为起点和终点的最长回文子串的长度 \(p_i - 1\),然后考虑维护以每个 # 为起点或者终点的最长回文串的长度,由于我们只需要 # 位置的信息,并且在 马拉车 的过程中已经求得了以某个点起点或者终点得最长的回文串的长度,那么对于以某个位置 \(i\) 为起点的最长回文串的长度就是以 \(i - 2\) 为起点的最长回文串的长度再减去 \(2\), 终点同理,最终枚举 # 求答案即可。
CODE
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 3E5 + 10;
int n, m, Case;
char s[N], str[N];
int p[N], Left[N], Right[N];
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
void manacher() {
int rt = 0, mid = 0;
for(int i = 1; i <= m; i ++ ) {
p[i] = i < rt ? min(p[2 * mid - i], rt - i) : 1;
while(str[i + p[i]] == str[i - p[i]]) ++ p[i];
if(i + p[i] > rt) {
rt = i + p[i];
mid = i;
}
chkmax(Left[i + p[i] - 1], p[i] - 1);
chkmax(Right[i - p[i] + 1], p[i] - 1);
}
}
int main() {
str[0] = '!', str[1] = '#';
scanf("%s", s);
n = strlen(s);
for(int i = 0; i < n; i ++ ) {
str[i * 2 + 2] = s[i];
str[i * 2 + 3] = '#';
}
m = n * 2 + 1;
str[m + 1] = '@';
manacher();
for (int i = 1; i <= m; i += 2) chkmax(Right[i], Right[i - 2] - 2);
for (int i = m; i >= 1; i -= 2) chkmax(Left[i], Left[i + 2] - 2);
int ans = 0;
for (int i = 1; i <= m; i += 2) if (Left[i] && Right[i]) {
chkmax(ans, Left[i] + Right[i]);
}
cout << ans << "\n";
return 0;
}