\(\\\)

\(Manacher\)


一种常用的字符串算法,用于处理一些回文字符相关的问题。

  • 回文串:从前向后和从后向前输出一致。
  • 回文中心:以这里开始,每次向外左右各扩展一个字符得到的回文串的中心。
  • 回文半径:定义在字符串的一个字符或两个字符的间隙上,代表以这里为回文中心的最长回文串的半径,具体的说,如果位置\(i\)延申出的最长回文串区间为\([l,r]\),那么他的回文半径就是\(r-i+1\)。

\(Manacher\)最基本的操作,就是求出以一个字符串的每一个字符和间隙为回文中心,对应的回文半径。

\(\\\)

\(\\\)

具体操作


\(\\\)

首先考虑间隙这个问题。由于便于描述间隙的回文半径,\(Manacher\)使用了一种巧妙的转化:

在每一个间隙(包括第一个字符前和最后一个字符后)各插上一个特殊字符,即原字符串中没有出现过的字符。为了不影响原串的正反匹配,所加的字符都是一样的。为了避免头尾匹配越界,头尾各放置一个与上述提到过的所有字符均不同的字符,且这两个字符也要不同。

我的方式一般是正常插入#,两侧分别插入不同的中括号。形象化的表示:

注意两侧多添加的字符只是为了匹配不越界,并不用于统计回文半径。

\(\\\)


\(\\\)

关于还原回原串的回文长度问题我们后面再讲,先考虑最优秀的复杂度求出新串所有位置的回文半径是多少。

线性。设\(len_i\)表示新字符串中以\(i\)为回文中心的回文半径,做的时候维护两个变量\(Maxr\)和\(Maxp\),第一个代表当前已经处理过的回文中心,其创造的回文子串最远延申到的字符位置,第二个就是对应的回文中心。

然后考虑如何快速的得到当前要求的位置\(i\)的答案,若\(i\not=1\),显然有\(i>Maxp\)。

我们可以用对称性很快的找到\(i\)关于\(Maxp\)的对称点\(i'\),并且之前已经确定了\(len_{i'}\)。观察下面的两种情况。

线段都表示以对应点为回文中心的最长回文子串。这一情况中,对称点的子串范围再对称回来并没有超过\(Maxp\)的子串范围,因为红色的大串是回文的,所以两侧的情况应该相同,即\(i\)所对应的回文子串长度应该与\(i'\)相同,\(len_i=len_{i'}\)。

这一情况中,我们一定能保证,以红色左端点为起点,以\(i'\)为对称中心的对称点为终点的部分,再关于\(Maxp\)对称过去是\(i\)可延伸的一个回文子串,但是再长因为超过了当前\(Maxr\)的范围,所以不能确定。这一情况下,先令\(len_i=len_{i'}\),再一个一个位置的尝试向外扩展。

当前位置直接超过\(Maxr\)时,我们也视作第二种情况。

当前位置的右端点超过\(Maxr\)时,更新\(Maxr\)和\(Maxp\)。

for(R int i=1,p=0,mr=0;i<=n;++i){
len[i]=i>mr?1:min(mr-i+1,len[(p<<1)-i]);
while(s[i-len[i]]==s[i+len[i]]) ++len[i];
if(i+len[i]-1>mr){p=i;mr=i+len[i]-1;}
}

预处理部分就不放代码了,核心其实就这四行。

\(\\\)

\(\\\)

复杂度证明和一些推论


\(\\\)

粗略的复杂度证明。扫描是\(\text O(N)\)的,每一个第一种情况得到答案是\(\text O(1)\)的,第二种情况至多只会出现\(N\)次,且每一次需要扩展右端点的扫描距离之和等于\(N\),这种类似于双指针扫描的东西复杂度是线性的。

\(\\\)


\(\\\)

有一个特殊的性质,即一个字符在原字符串的意义下\((\)#号即代表一个间隙\()\)做回文中心,他延申出的最长回文子串的长度,等于转化之后的字符串中,他的回文半径\(-1\)。我们分两种情况讨论证明它。

  • 转化后的字符为特殊字符\((\)#号\()\)。这一情况中得到的回文串,从这个#号开始的部分一定是形如#a#b....#z#的,注意到在不算中间字符时,右一半的#号和原串字符个数是一样的,左一半也是一样。因为中间字符不是原串字符,所以\(len-1\)就是以这个#号为回文中心,延伸出的最长回文子串的长度。
  • 原串的字符。这一情况得到的回文串,从这个字符开始的部分一定是形如a#b#.....z#的形式,这一情况下不算中间字符时,右一半#号个数比原串字符个数多一,左一半也是这样,所以右一侧回文半径中所有的#号都拿左侧的换,还多一个#号,正好用回文中心换掉。

关于上面的“一定是形如”部分,粗略的说明可以理解成,每个字符一定是被两个#号围着的,没有任何一个原串字符两侧出现除掉#号以外的其他字符。

\(\\\)


\(\\\)

还有一个定理可以得出,是一个字符串最多只有\(N\)个本质不同的回文子串。

考虑终点相同的回文子串 , 可以发现短的回文子串在大的回文子串中,因此对称过去在前面出现过。所以以某一个位置为终点且第一次出现的回文子串最多只有一个。这也是\(Manacher\)时间复杂度有保证的原因,因为如果\(Maxr\)不更新,就不会出现本质不同的回文子串,前面已经出现过了。而每扩展一次\(Maxr\),最多新出现一个本质不同的回文子串。

\(\\\)

\(\\\)

一道例题


\(\\\)

给出一个长度为\(N\)字符串,每个回文的部分都是一个碎片(碎片之间可部分重合),求最少多少次拼合碎片能够得出原字符串,拼合的定义是只要不完全相同就可以连接在一起,如果头尾有相同的部分可以重合。如\(aba\)和\(aca\)连接起来,可以生成串\(abaaca\)或 \(abaca\)。

  • \(N\in [5\times10^4]\),多组数据。

\(\\\)

\(Manacher\)板子。求出所有回文串在原字符串中的覆盖区间,就是最少线段完全覆盖问题,贪心即可。

关于为什么一定是最长回文子串的问题,考虑两个回文串想要重叠的部分,划给哪一侧另一侧都会失去那么长的长度,所以最长的回文子串可以代表所有子串的最优答案。

有一个化简是,注意到能覆盖一个字符,就一定能覆盖两侧的#号,所以直接统计在变化后的串每一个回文串的覆盖区间即可,不用还原回去。需要注意拼合的次数是总段数\(-1\)。

\(\\\)

#include<cmath>
#include<cstdio>
#include<cctype>
#include<string>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50010
#define R register
using namespace std; char s[N<<1],sr[N]; int p,mr,now,ans,slen,len[N<<1]; struct line{int l,r;}seg[N<<1]; inline bool cmp(line x,line y){return x.l<y.l;} inline void init(){
p=mr=ans=0;
s[now=1]='#';
slen=strlen(sr);
for(R int i=0;i<slen;++i){s[++now]=sr[i];s[++now]='#';}
s[0]='['; s[now+1]=']';
} inline void manacher(){
for(R int i=1;i<=now;++i){
len[i]=(i>mr)?1:min(mr-i+1,len[(p<<1)-i]);
while(s[i-len[i]]==s[i+len[i]]) ++len[i];
if(i+len[i]-1>mr){mr=i+len[i]-1;p=i;}
seg[i].l=i-len[i]+1; seg[i].r=i+len[i]-1;
}
} inline void calc(){
sort(seg+1,seg+1+now,cmp);
for(R int i=1,nowr=1,tmp=0;i<=now;){
while(seg[i].l<=nowr&&i<=now) tmp=max(tmp,seg[i].r),++i;
if(tmp+1<=nowr) break;
++ans; nowr=tmp+1;
}
printf("%d\n",ans-1);
} int main(){
while(scanf("%s",sr)!=EOF){init();manacher();calc();}
return 0;
}

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

  1. Manacher学习笔记

    目录 code(伪) Manacher算法 可在 \(O(n)\)的时间内求出一个字符串以每个位置为中心的最长回文子串. 原理:根据之前预处理出的回文串长度求得新的回文串长度 我们可以通过在字符中加上 ...

  2. Manacher算法学习笔记 | LeetCode#5

    Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...

  3. 学习笔记 - Manacher算法

    Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...

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

    [学习笔记]字符串-马拉车(Manacher) 一:[前言] 马拉车用于求解连续回文子串问题,效率极高. 其核心思想与 \(kmp\) 类似:继承. --引自 \(yyx\) 学姐 二:[算法原理] ...

  5. OI知识点|NOIP考点|省选考点|教程与学习笔记合集

    点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...

  6. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  7. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  8. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  9. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

随机推荐

  1. 【Codeforces 1009C】Annoying Present

    [链接] 我是链接,点我呀:) [题意] 题意 [题解] 其实就是让你最后这n个数字的和最大. 加上的x没有关系.因为肯定都是加上n个x 所以直接加上就可以了 主要在于如何选取j 显然我们要找到一个位 ...

  2. POJ 2356 && POJ 3370 鸽巢原理

    POJ 2356: 题目大意: 给定n个数,希望在这n个数中找到一些数的和是n的倍数,输出任意一种数的序列,找不到则输出0 这里首先要确定这道题的解是必然存在的 利用一个 sum[i]保存前 i 个数 ...

  3. 【Tomcat】Tomcat替换猫的图片

    参考:网页title上添加图片 直接替换Tomcat安装目录下ROOT下面的favicon.ico图标(名字与前面一样favicon.ico)

  4. mysql MVCC原理理解

    MVCC多版本控制: 指的是一种提高并发的技术.最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞.引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了Inno ...

  5. - > 动规讲解基础讲解四——矩阵取数

    给定一个m行n列的矩阵,矩阵每个元素是一个正整数,你现在在左上角(第一行第一列),你需要走到右下角(第m行,第n列),每次只能朝右或者下走到相邻的位置,不能走出矩阵.走过的数的总和作为你的得分,求最大 ...

  6. SIGSEGV 和 SIGBUS & gdb看汇编

    参考这篇文章: http://blog.chinaunix.net/uid-24599332-id-2122898.html SIGBUS和SIGSEGV也许是我们在平时遇到的次数最多的两个内存错误信 ...

  7. 这篇讲angular 的$q的讲得不错

    原文: https://segmentfault.com/a/1190000000402555 ---------------------------------------------------- ...

  8. Cisco路由器配置ADSL上网

    cisco1841#sh run Building configuration... Current configuration : 2970 bytes ! version 12.4 service ...

  9. PHPthinking官方论坛招募版主

    时间飞逝.就在昨天,我们PHPthinking的官方论坛刚刚上线了我们自己的论坛! 欢迎大家注冊账号,活跃在论坛的大家庭中,我们会及时关注论坛公布的全部内容.在开发学习的过程中,遇到的不论什么问题,有 ...

  10. 分布式软件体系结构风格(C/S,B/S)

    分布式软件体系结构风格 1.  三层C/S结构 2.  三层B/S结构 了解很多其它软件体系结构 三层C/S结构(3-Tier C/S Architecture) §第1层:用户界面GUI-表示层-- ...