【学习笔记】字符串—马拉车(Manacher)

一:【前言】

马拉车用于求解连续回文子串问题,效率极高。

其核心思想与 \(kmp\) 类似:继承。 ——引自 \(yyx\) 学姐


二:【算法原理】

对于任意一个回文串 \(a\),设其中点为 \(mid\)(为方便描述,偶数串则在正中央加一个位置),那么根据定义,有:

\(a[mid-1]==a[mid+1]\)

\(a[mid-2]==a[mid+2]\)

\(...\)

可知:

如果 \(a[mid-x]\) 可以形成半径为 \(r\) 的回文串,且不越过以 \(mid\) 为中心的回文串,那么 \(a[mid+x]\) 也可以形成半径为 \(r\) 的回文串。

以奇回文串为例,用 \(r[i]\) 表示以 \(i\) 为中点的最大回文串长度。

如果我们已知 \(r[mid],r[j](j \in [mid-r[mid]+1,mid-1])\),那么可以分两种情况推出其关于 \(mid\) 的对称点 \(i\) \((\frac {i+j}{2}=mid)\) 的半径:

设 \(R=mid+r[mid]-1\) 。

\((1).\) \(i+r[j]-1<=R,\) \(r[i]=r[j]\) 。

\((2).\) \(i+r[j]-1>R,\) \(r[i]=R-i+1\) 。

即 \(r[i]=min(r[j],R-i+1)\) 。

如上所述,实现了从前面信息到后面信息的继承

继承之后还需要判断以 \(i\) 为中心能否继续扩张,这时候可以直接暴力枚举扩大半径


三:【算法实现】

实时维护一个已知覆盖范围最靠右的回文串信息,记录其中点 \(mid\) 和右端点 \(R\)。

从 \(1,n\) 枚举 \(i\):

若 \(i<=R\) 则可以用 \(i\) 的对称点 \(mid*2-i\) 得到 \(r[i]\),

若 \(i>R\)(即 \(R=i-1\) 的情况,因为 \(R\) 永远大于等于 \(i-1\)),初始化 \(r[i]=1\),然后暴力扩大求出最大 \(r[i]\)。

如果以 \(i\) 为中点可以得到更靠右的回文串,更新 \(mid\) 和 \(R\)。

但偶数串不太好处理,所以在原字符串中的所有空隙中插入一个不可能出现的字符,例如 \('*',\) \('|'\) 等等,最前面和最后面也要插。

此时,奇数串还是奇数串偶数串则变成了奇数串,可以统一按照奇数串处理啦。

注意:统计答案时应取真实的回文串长度。

(具体可以自己画个图分类讨论一下,会发现无论奇偶,无论是否为原字符串中的字符,\(r[i]-1\) 始终等于以 \(i\) 为中点的实际最大回文串长度。)

【Code】

题目:\(Palindrome\) \([Poj3974]\)

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<string>
#define Re register int
using namespace std;
const int N=1e6+5;
int n,R,mid,ans,OOO,r[N<<1];char op[N],a[N<<1];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
int main(){
while(cin>>op){
if(op[0]=='E'&&op[1]=='N'&&op[2]=='D')break;//没用string就只能这样一位一位地判断了
a[0]='$',a[n=1]='*',R=0,mid=0,ans=0;
for(Re i=0;op[i];++i)a[++n]=op[i],r[n]=0,a[++n]='*',r[n]=0;//玄学填空法
for(Re i=1;i<=n;++i){
r[i]=i<=R?min(R-i+1,r[(mid<<1)-i]):1;//继承前辈的信息
while(a[i-r[i]]==a[i+r[i]])++r[i];//暴力扩张领域
if(i+r[i]-1>R)R=i+r[mid=i]-1;
ans=max(ans,r[i]-1);//取实际回文长度
}
printf("Case %d: %d\n",++OOO,ans);
}
}

四:【时间复杂度】

似乎看起来效率并不高,近似 \(O(n^2)\),但实际上它的理论复杂度是 \(O(n)\)。

为何?

对于每个 \(i\):

如果 \(i<=R\),那么直接 \(O(1)\) 计算 \(r[i]\)。

如果 \(i>R\),那么会用 \(O(len)\) 向右扫描 \(len\)个单位,所以此时 \(R\) 会向右移动 \(len+1\) 的单位。

而 \(R\) 最多只会移动 \(n\) 个单位,因此 \(Manacher\) 算法时间复杂度是线性的:\(O(n)\)。


五:【例题】

最新文章

  1. oracle 按时间段统计15分钟内的数据
  2. evil program
  3. php与ascii码
  4. 一秒钟生成自己的iOS客户端
  5. Android 之 2048 的游戏逻辑分析
  6. SQL Server 收缩数据库
  7. 详解linux运维工程师入门级必备技能
  8. pybot/robot命令参数说明
  9. 单片机C语言下LCD多级菜单的一种实现方法
  10. C#获取数组的行和列数程序代码
  11. python内置函数(4)
  12. uva12096 The SetStack Computer By sixleaves
  13. Hibernate 多表关联映射- Hibernate中使用的集合类型(set,list,array,bag,map)
  14. MapReduce(十五): 从HDFS阅读本文的源代码分析
  15. linux上的组管理
  16. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki
  17. DPDK - gsg文档(介绍与编译)
  18. CF 859E Desk Disorder
  19. ASP.NET 身份验证机制
  20. 声明元素&lt;%! %&gt;、Scriptlet元素&lt;% %&gt;、表达式元素&lt;%= %&gt;、注释元素、输出特殊符号&lt;%和%&gt;

热门文章

  1. 设计模式之设计原则 C#
  2. 消息服务dubbo接口性能压测性能优化案例
  3. pm2入门
  4. python之滑动认证(图片)
  5. pymysql之有序字典
  6. [MySQL] 事务的ACID特性
  7. android内存管理-ION/PMEM【转】
  8. 以太网PHY寄存器分析【转】
  9. jmeter压测学习4-正则表达式提取
  10. php、mysql查询当天,查询本周,查询本月的数据实例(字段是时间戳)