使用时间复杂度 \(O(n^3)\) 的代码显然是无法通过的,需要效率更高的代码。
这里,我们需要用到一种manacher 算法,它有一个用途:在 \(O(n)\) 的线性时间复杂度内找到一个字符串的最长回文子串的长度。
首先,我们要将每个字符之间和字符串的两端插入 # 字符,从而使这个字符串变为奇回文串,且在下标为 \(0\) 的位置插入哨兵 $,方便统一处理。
abcba 变为 $#a#b#c#b#a#。abba 变为 $#a#b#b#a#。接着,我们引入一个概念:回文半径 \(d\) 函数。
回文半径是指以 \(i\) 为中心的最长回文串长度的一半,向上取整。
| name | value |
|---|---|
| \(str\) | # a # b # a # b # |
| \(i\) | 1,2,3,4,5,6,7,8,9 |
| \(d_i\) | 1,2,1,4,1,4,1,2,1 |
一个字符串的最长回文子串的长度,就是改造串的最长回文半径减 \(1\)。
知道了最长回文子串的长度的求法,我们就来看看怎样求回文半径 \(d\) 函数。
再来学习一个概念:加速盒子,区间 \([l,r]\)。
在盒内(即 \(i \leq r\)),我们可以借助回文子串的对称点的 \(d_i\) 进行直接转移,例如 \(i=5\),此时 \(l=1\),\(r=7\),中心为 \(4\),我们发现,它的对称点就是 \(r-i+l\)。
接下来分情况讨论:
在盒外(即 \(i>r\)),则从 \(i\) 开始枚举。
求出 \(d_i\) 后,若 \(i+d_i-1>r\),则修改区间左右端点:
#include <bits/stdc++.h>
using namespace std;
char a[30000000],s[30000000];
int n,k,l,r,d[30000000],maxn;
// manacher的实现
void manacher(char *s,int n)
{
// 先初始化d[1]和r,此处r取0或1都可以,只要保证第一个if不执行
d[1]=1;
r=1;
for(int i=2;i<=n;i++)
{
// 判断是否在盒内
if(i<=r)
{
// 将d[i]进行转移
d[i]=min(d[r-i+l],r-i+1);
}
// 判断对称点是否相等,相等便继续枚举
while(s[i-d[i]]==s[i+d[i]])
{
d[i]++;
}
// 修改区间左右端点
if(i+d[i]-1>r)
{
l=i-d[i]+1;
r=i+d[i]-1;
}
// 找到改造串最长回文半径-1,即最长回文子串的长度
maxn=max(maxn,d[i]-1);
}
}
int main()
{
// 从下标为1的位置开始,输入原字符串a
scanf("%s",a+1);
n=strlen(a+1);
// 构建改造奇回文串
s[0]='$';
s[++k]='#';
for(int i=1;i<=n;i++)
{
s[++k]=a[i];
s[++k]='#';
}
// manacher
manacher(s,k);
cout << maxn;
return 0;
}