@(学习笔记)[Manacher]
问题概述
给定一个串(str), 要求出其以每一位为对称中心的最长回文串的长度
解决方法
思路: 充分利用已有信息, 做到(O(n))复杂度.
首先明确一下字符串(str)的一个子串的表示方式: (str[i ... j] (i le j))表示原串从第(i)位到第(j)位形成的子串; (str[j ... i] (i le j))反之.
我们发现回文串有两种, 一种长度为奇数(如(ababa)), 另一种长度为偶数(如(abba)). 它们的对称中心是不一样的, 分别是一个字母和两个字母见的空隙. 为了避免分类讨论, 我们在每两个字母之间插入一个分隔符, 这样就只要考虑长度为奇数的回文串了. 我们令p[i]
表示以第(i)位为对称中心的回文串向两边延伸的最大长度(包含对称中心, 例如串(a|b|b|a)中(p[4] = 4)). 假设我们已经处理出(p[0 ... i - 1])的值, 我们记录下(p[j in [0, i - 1]] + j)的最大值mx
, 和以(mx)为结尾的回文串的对称中心id
. 对于当前一位(i), 不难发现, (str[i ... mx])与(str[id cdot 2 - i ... id - p[id]])在位置上关于(id)对称, 因此它们不超出([id - p[id], id + [id]])的部分是相等的. 我们只需要在已有的这一部分的基础上继续往外扩张匹配, 然后再更新(mx)和(id)即可.
#include <cstdio>
#include <cctype>
#include <algorithm>
const int LEN = 1 << 20;
namespace Zeonfai
{
inline char getChar()
{
char c;
for(c = getchar(); ~ c; c = getchar());
return c;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("manacher.in", "r", stdin);
freopen("manacher.out", "w", stdout);
#endif
using namespace Zeonfai;
static char str[LEN << 1];
int len = 0;
str[len ++] = '<', str[len ++] = '#';
char c;
while(~ (c = getchar()))
str[len ++] = c, str[len ++] = '#';
str[len ++] = '>';
int mx = 0, id = 0, ans = 0;
static int p[LEN << 1];
for(int i = 0; i < len; ++ i)
{
p[i] = mx > i ? std::min(mx - i, p[(id << 1) - i]) : 1;
for(; str[i - p[i]] == str[i + p[i]]; ++ p[i]);
if(p[i] + i > mx)
mx = p[i] + i, id = i;
ans = std::max(ans, p[i]);
}
printf("%d", ans - 1);
}