洛谷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可分为aacaabbacabb两部分,且两者都是回文串。

对于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;
}
posted @ 2022-10-13 14:09  ccz9729  阅读(55)  评论(0编辑  收藏  举报