Manacher算法 - 学习笔记

是从最近Codeforces的一场比赛了解到这个算法的~

非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法

我在 vjudge 上开了一个〔练习〕,有兴趣的reader们可以参考一下 \(QwQ\)


『算法简述』

一个思路比较简单但非常有效的字符串算法(其实不止字符串,反正就是用来求回文的),用于求给定字符串中的回文子串,有一些研究者证明了它的时间复杂度均摊下来是 \(O(n)\) 的,只可惜我看不懂他们怎么证明的……

中文名叫“马拉车”算法(或许是音译过来的),它的想法非常简单,只是利用了之前求解到的回文串。

首先我们需要对原字符串str进行一个操作——假如原串是 \(str=s_0s_1s_2...s_l\),那么我们定义两个不同的元素 \(a,b\) ,且 \(a,b\) 不等于任何一个 \(s_i\)。那么我们把原串改成 \(mdy=abs_0bs_1bs_2b...bs_lb\),可以发现 \(mdy\) 中若存在回文子串,那么回文子串的长度一定是奇数[1],这样会方便一点。可见修改过后字符串的长度变成了 原长*2+2;但是要注意我们一般都把数组设置为从0开始

接下来我们定义 haf[i] 表示以 i 为中心的回文串的最长半径,比如 "abcba" 的haf[2]=3。这样我们就可以表示一个以i为中心的最长的回文子串了!

下面就是马拉车算法的精华——定义 \(Rig\) 为当前找到的回文子串中右端点的最大值,\(Id\) 为 \(Rig\) 对应的回文子串的中心位置。

我们枚举回文子串的中心位置 i ,如果 i<Rig ,那么 i 就一定被包含在一个回文子串里[2],那么我们找到 i 关于 Id 的对称位置即 \(j=(2*Id-i)\) ,可以算出以j为中心的被包含在以Id为中心的最大回文子串中的最大子串长度(我知道说起来有一点晕,但是相信 reader 们看了例子就会明白),举个例子:

原串为 "1323141323" ,现在 i=8 ,那么 Rig=9,Id=5(对应的子串为 "323141323")

找到对称位置 j=2 ,找到以 j 为中心的包含在 "323141323" 中的最大回文子串 "323" (不能是 "13231",因为左边的 "1" 在 "32314323" 外

那么我们可以知道因为 i,j 关于 Id 对称,所以以 i 为中心的回文子串的半径至少是 min(haf[j],Rig-i)(取min是为了限制找到的串在以 Id 为中心的回文子串中)。但是在这个基础上,我们可能可以继续扩充——继续枚举检验两边的字符是否相同,如果相同则可以扩展。

枚举完为止~

看起来马拉车算法局限性比较强,但实际上可以在回文串的限制上有很多变化——甚至加上一些单调栈、线段树之类的优化!至于具体哪些地方可能会用其他的算法我会在模板代码里注释出来。


『例题』

一、〔HDU 3068 - 最长回文〕

(也可以是 URAL - 1297,只是一个输出具体的子串,另一个只输出长度)

如果原串是从0开始存储的话,我们可以在 Manacher 中算得 haf 的最大值 resmax,以及它对应的中心位置 resmid —— 略找规律,我们可以发现在原串中,这个回文子串起始于 \((resmid-resmax)/2\),长度为 \((resmax-1)\)

二、〔HDU 4513 - 完美队形II 〕

我的思路大概就是先预处理出 low[i] 表示以 i 为结尾的最长的不下降子串的长度(不是序列!必须连续!),然后找出以当前位置为中心的最长回文串,再判断回文子串中的左半部分的不下降长度,相应的,子串的右半部分就是不上升的了~


『源代码』

模板代码:

int haf[LEN*2+10]; //LEN是原串的长度
int Manacher(string str){
string mdy="-+"; //a='-' , b='+'
for(int i=0;i<str.length();i++)
mdy+=str[i],mdy+='+';
int Rig=0,Id=0;
for(int i=1;i<mdy.length();i++){ //注意这里从1开始,忽略开头的'-'
if(i<Rig) haf[i]=min(haf[Id*2-i],Rig-i);
else haf[i]=1; //i本身构成一个回文子串
while(mdy[i-haf[i]]==mdy[i+haf[i]]){
haf[i]++;
/*
这里经常会进行一些其他操作;
*/
}
if(i+haf[i]>Rig) Rig=i+haf[i],Id=i;
/*
这里存储答案;
这里也经常进行其他操作;
*/
}
/*
求解完回文子串后可能还要处理一些东西~
*/
}

HDU 3068 - 最长回文

/*Lucky_Glass*/
#include<bits/stdc++.h>
using namespace std;
const int SIZ=110000*2;
char str[SIZ+5],mdy[SIZ*2+5];
int haf[SIZ+5];
int Manacher(){
mdy[0]='-';mdy[1]='+';
int lenstr=strlen(str);
for(int i=0;i<lenstr;i++)
mdy[2*i+2]=str[i],mdy[2*i+3]='+';
mdy[2*lenstr+2]='$';
int reslen=0,resmid,Rig=0,Id=0;
for(int i=1;i<lenstr*2+2;i++){
if(i<=Rig) haf[i]=min(haf[2*Id-i],Rig-i);
else haf[i]=1;
while(mdy[i-haf[i]]==mdy[i+haf[i]]) haf[i]++;
if(i+haf[i]>Rig){
Rig=i+haf[i];
Id=i;
}
if(reslen<haf[i]){
reslen=haf[i];
resmid=i;
}
}
return reslen-1;
}
int main(){
while(~scanf("%s",str)){
printf("%d\n",Manacher());
}
return 0;
}

HDU 4513 - 完美队形II

/*Lucky_Glass*/
#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int Cas,n;
int hgt[N+5],mem[N*2+5],low[N+5],haf[N*2+5];
int Manacher(){
int Rig=0,Id,ret=0;
for(int i=1;i<n*2+2;i++){
if(i<Rig) haf[i]=min(Rig-i,haf[Id*2-i]);
else haf[i]=1;
while(mem[i-haf[i]]==mem[i+haf[i]]) haf[i]++;
if(i+haf[i]>Rig){Rig=i+haf[i];Id=i;}
int len=haf[i]-1,mid=i;
int lef=(mid-len)/2+1;len;
if(len&1){
int fhaf=len/2,fmid=lef+len/2;
fhaf=min(fhaf,low[fmid]-1);
ret=max(ret,fhaf*2+1);
}
else{
int fhaf=len/2,fmid=lef+len/2-1;
fhaf=min(fhaf,low[fmid]);
ret=max(ret,fhaf*2);
}
}
return ret;
}
int main(){
scanf("%d",&Cas);
for(int cas=1;cas<=Cas;cas++){
memset(mem,0,sizeof mem);
memset(low,0,sizeof low);
scanf("%d",&n);
mem[0]=-1;mem[1]=-2;
for(int i=1;i<=n;i++){
scanf("%d",&hgt[i]);
if(hgt[i-1]<=hgt[i]) low[i]=low[i-1];
low[i]++;
mem[i*2]=hgt[i];mem[i*2+1]=-2;
}
printf("%d\n",Manacher());
}
return 0;
}

\(\mathcal{The\ End}\)

\(\mathcal{Thanks\ For\ Reading!}\)

如果有什么没看懂的可以在我的邮箱 \(lucky\_glass@foxmail.com\) 上问,我会定期查看邮箱并尽可能地解决问题!


  1. 简单的举个例子:\(str=abba\),假设 \(a='@',b='|'\),那么修改过后的字符串就是 \(mdy=@|a|b|b|a|\),可见任意一个回文子串(例如 \(|b|b|\))都是奇数的长度; ↩︎

  2. 其实这里隐含了一个条件:Id<i,因为 Id 是在枚举到 i 之前计算出来的,所以一定小于 i ; ↩︎

学习笔记 - Manacher算法的更多相关文章

  1. [ML学习笔记] XGBoost算法

    [ML学习笔记] XGBoost算法 回归树 决策树可用于分类和回归,分类的结果是离散值(类别),回归的结果是连续值(数值),但本质都是特征(feature)到结果/标签(label)之间的映射. 这 ...

  2. 学习笔记——EM算法

    EM算法是一种迭代算法,用于含有隐变量(hidden variable)的概率模型参数的极大似然估计,或极大后验概率估计.EM算法的每次迭代由两步组成:E步,求期望(expectation):M步,求 ...

  3. 数据挖掘学习笔记--AdaBoost算法(一)

    声明: 这篇笔记是自己对AdaBoost原理的一些理解,如果有错,还望指正,俯谢- 背景: AdaBoost算法,这个算法思路简单,但是论文真是各种晦涩啊-,以下是自己看了A Short Introd ...

  4. 学习笔记-KMP算法

    按照学习计划和TimeMachine学长的推荐,学习了一下KMP算法. 昨晚晚自习下课前粗略的看了看,发现根本理解不了高端的next数组啊有木有,不过好在在今天系统的学习了之后感觉是有很大提升的了,起 ...

  5. Java学习笔记——排序算法之快速排序

    会当凌绝顶,一览众山小. --望岳 如果说有哪个排序算法不能不会,那就是快速排序(Quick Sort)了 快速排序简单而高效,是最适合学习的进阶排序算法. 直接上代码: public class Q ...

  6. Java学习笔记——排序算法之进阶排序(堆排序与分治并归排序)

    春蚕到死丝方尽,蜡炬成灰泪始干 --无题 这里介绍两个比较难的算法: 1.堆排序 2.分治并归排序 先说堆. 这里请大家先自行了解完全二叉树的数据结构. 堆是完全二叉树.大顶堆是在堆中,任意双亲值都大 ...

  7. Java学习笔记——排序算法之希尔排序(Shell Sort)

    落日楼头,断鸿声里,江南游子.把吴钩看了,栏杆拍遍,无人会,登临意. --水龙吟·登建康赏心亭 希尔算法是希尔(D.L.Shell)于1959年提出的一种排序算法.是第一个时间复杂度突破O(n²)的算 ...

  8. 算法笔记--manacher算法

    参考:https://www.cnblogs.com/grandyang/p/4475985.html#undefined 模板: ; int p[N]; string manacher(string ...

  9. 学习笔记——SM2算法原理及实现

    RSA算法的危机在于其存在亚指数算法,对ECC算法而言一般没有亚指数攻击算法 SM2椭圆曲线公钥密码算法:我国自主知识产权的商用密码算法,是ECC(Elliptic Curve Cryptosyste ...

随机推荐

  1. 使用electron构建跨平台Node.js桌面应用

    最近,把团队内经常使用的一个基于Node.js制作的小工具给做成了可视化操作的桌面软件,使用的是electron,这里简单分享一下使用electron的一些经验和心得. 一.如何使用electron把 ...

  2. Qt 之 QSS(样式表语法)

    https://blog.csdn.net/liang19890820/article/details/51691212 简述 Qt样式表(以下统称QSS)的术语和语法规则几乎和CSS相同.如果你熟悉 ...

  3. pymsql模块使用

    数据库连接客户端 链接:https://pan.baidu.com/s/1pM0h4SV 密码:614v  sql指令基本用法:

  4. gitlab 创建ssh步骤

    1.使用gitlab首先要创建ssh的公钥和私钥.创建ssh的步骤和命令如下: 打开git bash,我的是windows,切换到.ssh文件夹下.如果没有则创建.ssh(默认是没有这个文件夹的).检 ...

  5. SQL Server ->> Sparse File(稀疏文件)

    Sparse File(稀疏文件)不是SQL Server的特性.它属于Windows的NTFS文件系统的一个特性.如果某个大文件中的数据包含着大量“0数据”(这个应该从二进制上看),这样的文件就可以 ...

  6. 线程 Z

    原文:http://www.albahari.com/threading/part5.aspx 专题:C#中的多线程 1并行编程Permalink 在这一部分,我们讨论 Framework 4.0 加 ...

  7. laravel 使用EasyWechat 3分钟完成微信支付(以APP支付为例)

    上一篇写了支付宝支付,然后这段时间我又把微信支付给接上了,作为萌新的我还是很有成就感的,哈哈~~好了,该写正事了. 第一步:创建应用及配配置  首先到微信的官方平台注册应用https://pay.we ...

  8. Hyperledger Fabric 1.0 学习搭建 (三)--- 运行测试e2e-Fabric

    3.1.运行fabric-samples的问题说明 该问题说明能够解决6.1.平台特定使用的二进制文件配置第一步的问题.可以选择继续阅读该说明,或者等参考到6.1小节时再反向阅读本说明,具体在6.1中 ...

  9. memcached 相关

    今天用了下memcached,把一个日志分析结果的大数组缓存起来,由于实时性跟准确性要求不高,所以缓存一周:因为日志越来越多,不缓存的话每次查看页面会比较慢.(其实可以先离线定期计算好结果存起来).以 ...

  10. JavaScript中如何判断两变量是否“相等”?

    1 为什么要判断? 可能有些同学看到这个标题就会产生疑惑,为什么我们要判断JavaScript中的两个变量是否相等,JavaScript不是已经提供了双等号“==”以及三等号“===”给我们使用了吗? ...