Manacher
Manacher
下面的叙述中,约定字符串下标从
定义
Manacher 算法应用于一个特定场景:静态求一个字符串的最长回文子串。复杂度
首先考虑暴力法:枚举中心点,向左右扩展,判断它左右对称的位置是否相同。暴力法的复杂度上界显然是
考虑暴力法低效的原因,是因为有大量的重复检查。Manacher 就是利用回文串的性质,通过辅助数组
实现
首先,因为回文串分长度为奇数和偶数两种情况,所以先对字符串做一个特殊处理:在
然后是 Manacher 算法的核心:定义数组
假设已经计算出了
下面计算
- 若
,由于 右边的字符还没有检查过,所以只能暴力扩展; - 若
,则按照 和回文串 的左端点的位置关系细分为两种情况:若 在 的左端点右侧,因为以 为结尾的回文串不会越过 ,得 作为初始值,然后暴力扩展完成 的计算;若 在 的左端点左侧,则只能先计算在 内的 部分作为初始值,然后再用暴力中心扩展法。
实际代码实现中,这两种情况可以合并计算。
代码(P3805 【模板】manacher)
比较好的一点是,遇到多测,
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=1.1e7+5;
int n,P[MAXN<<1];
string s1,s;
void init(){
s+="$#";
for(auto x:s1) s+=x,s+='#';
s+='&';
n=s.size();
}
// 背板!
void manacher(){
int R=0,C=0;
for(int i=1;i<n;i++){
P[i]=i<R?min(P[(C<<1)-i],P[C]+C-i):1;
while(s[i+P[i]]==s[i-P[i]]) P[i]++;
if(R<P[i]+i) R=P[i]+i,C=i;
}
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>s1;
init();
manacher();
cout<<*max_element(P,P+n)-1<<'\n';
return 0;
}
复杂度
实际上,Manacher 算法的本质是在扩展
后记
实际上,Manacher 算法可以求解具有回文串类似的性质的问题,也就是满足
void manacher(){
int R=0,C=0;
for(int i=1;i<n;i++){
P[i]=i<R?min(P[(C<<1)-i],P[C]+C-i):check(i,i);
while(check(i-P[i],i+P[i])) P[i]++;
if(R<P[i]+i) R=P[i]+i,C=i;
}
}
注意边界情况的特判。
例题
-
求出原串的所有回文子串,记录右端点,问题即转化为用最少的线段覆盖整个区间的贪心问题。贪心地记录最右端点即可。
-
求出
后,统计每种长度的回文串的出现次数,如果 比回文串的种类多则无解。考虑到如果有长度为 的回文串则一定有长度为 的回文串,所以求后缀和就可以知道每种串的出现次数。长度为 的回文串给答案的贡献是 ,其中 是它的出现次数。 -
二维回文,略显毒瘤。考虑一个合法正方形的充要条件就是每一行和每一列都是回文串,那么先对每一行和每一列各跑一边 Manacher 并记录下
。然后考虑到以一个点为中心,如果在横向上能扩展半径为 的单位,那么这些单位在纵向上的最长回文半径肯定是不能小于 的。于是用二分找到以每个点为中心能横向扩展的最长长度。纵向同理。然后对于每个中心的横向/纵向最长扩展长度取 并累加即可。注意我们依然需要在字符间添加 字符来统计,但最后能作为中心的 字符仅包含满足其左上/左下/右上/右下都是数字的 字符。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】