Manacher
Manacher算法是一种用于查找字符串中最长回文子串的线性时间复杂度算法。
算法原理
Manacher算法的核心思想是利用回文串的对称性来避免重复计算。它通过维护一个"最右回文边界"和对应的中心点,来高效地扩展回文半径。
算法步骤
1. 预处理扩展字符串
为了转化偶数长度的回文串为奇数长度的回文串,我们在字符串中的每个字符中间插入一个不会有的字符(如‘|’‘&’),然后再在字符串前插入一个奇奇怪怪没有用过的字符。
代码如下:
//读入原字符串str
data[0]='~',data[1]='|';
for(int i=0; i<str.size(); i++)
{
data[++cnt]=str[i],data[++cnt]='|';
}
//data即为扩展字符串
2. 线性处理回文半径
记一个最右的回文串右端点 rrr 即其对应的回文中心 ccc,然后分四种情况处理(为方便表示,记以 ccc 为回文中心的回文串为 strcstr_cstrc):
- 若当前 ttt 超过了 111 到 i−1i-1i−1 最大的回文串的范围,则暴力扩展求 PtP_tPt;
- 若没超过,且 ttt 的关于 ccc 的对称点 2c−t2c-t2c−t 所对应的回文串 str2×c−tstr_{2×c-t}str2×c−t 包含在 strcstr_cstrc 内,则直接令 Pt=P2×c−tP_t=P_{2×c-t}Pt=P2×c−t;
- 若 ttt 的对应点所对应的回文串部分超出 strcstr_{c}strc,则令 Pt=r−t+1P_t=r-t+1Pt=r−t+1;
- 若 str2×c−tstr_{2×c-t}str2×c−t 压在 strcstr_cstrc 的边界上,则让 Pt=2×c−tP_t=2×c-tPt=2×c−t 后暴力继续扩展
为了方便,在具体实现中,我把四种情况并起来写。
代码如下:
for(long long t=1,r=0,mid=0; t<=cnt; t++)
{
if(t<=r) p[t]=min(p[(mid<<1)-t],r-t+1);
while(data[t-p[t]]==data[t+p[t]]) ++p[t];
if(p[t]+t>r) r=p[t]+t-1,mid=t;
}
3.映射关系
找规律发现,Pt−1P_t-1Pt−1 即为以 ttt 为回文中心的最长回文串的长度(注意 PtP_tPt 映射的是扩展串的第 ttt 位)
模板题代码实现
#include<bits/stdc++.h>
using namespace std;
const long long maxn=11000005;
char data[maxn<<1];
long long p[maxn<<1],cnt=1,ans;
string str;
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>str;
data[0]='~',data[1]='|';
for(int i=0; i<str.size(); i++)
{
data[++cnt]=str[i],data[++cnt]='|';
}
for(long long t=1,r=0,mid=0; t<=cnt; t++)
{
if(t<=r) p[t]=min(p[(mid<<1)-t],r-t+1);
while(data[t-p[t]]==data[t+p[t]]) ++p[t];
if(p[t]+t>r) r=p[t]+t-1,mid=t;
if(p[t]>ans) ans=p[t];
}
cout<<ans-1;
return 0;
}
复杂度证明
或许某些大佬看到上面的代码后会想到 O(n)O(n)O(n) 和 O(n2)O(n^2)O(n2) 的均摊,但是实际上不是的:rrr 是一直向右运行的,而 rrr 最多能够运行到 nnn,所以整体的时间复杂度应为 O(n)O(n)O(n).
注:本文为编者手搓,如有笔误敬请指出。

被折叠的 条评论
为什么被折叠?



