【学习笔记】字符串—马拉车(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)\)。


五:【例题】

【学习笔记】字符串—马拉车(Manacher)的更多相关文章

  1. 《python基础教程(第二版)》学习笔记 字符串(第3章)

    <python基础教程(第二版)>学习笔记 字符串(第3章)所有的基本的序列操作(索引,分片,乘法,判断成员资格,求长度,求最大最小值)对字符串也适用.字符串是不可以改变的:格式化输出字符 ...

  2. 【Redis】命令学习笔记——字符串(String)(23个超全字典版)

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). 本篇基于redis 4.0.11版本,学习字符串( ...

  3. es6学习笔记--字符串&数值&数组&函数&对象的扩展

    这几天抽空学习了es6语法,关于字符串,数值,数组,函数以及对象的扩展,看到es6标准入门这本书,里面讲的扩展特别多,我认为有几部分在项目上用不到,就挑有用的当笔记学习了. 字符串的扩展 str.in ...

  4. Java学习笔记--字符串和文件IO

    1.Java中的字符串类和字符的表示 2.区分String,StringBuilder和StringBuffer 3.从命令行中给main方法传递参数 4.文件操作 1 Java中的字符串和字符 1. ...

  5. Python学习笔记-字符串

    Python之使用字符串 1.所有的标准序列操作(索引,分片,乘法,判断成员资格,求长度,取最小值,最大值)对字符串同样适用.但是字符串都是不可变的. 2.字符串格式化使用字符串格式化操作符即%. f ...

  6. Java-J2SE学习笔记-字符串转化为二维数组

    1.字符串转化为二维Double数组 2.代码: package Test; public class TestDouble { public static void main(String[] ar ...

  7. Java学习笔记——字符串常用函数

    class JavaTest4_String { public static void main(String[] args) { String str1 = "IOS,ANDROID,BB ...

  8. C语言学习笔记--字符串函数

    字符串函数 需要包含头文件#include<stdio.h> strlen strcmp strcpy strchr strstr strcasestr

  9. 【学习】js学习笔记---字符串对象

    一.属性 1.length 字符串的长度,且不区分中英文的字节. 示例代码: var str="abcdefghijklmn"; var str1="中文汉语" ...

  10. Python学习笔记 - 字符串和编码

    #!/usr/bin/env python3 # -*- coding: utf-8 -*- #第一行注释是为了告诉Linux/OS X系统, #这是一个Python可执行程序,Windows系统会忽 ...

随机推荐

  1. 前端开发HTML5——表单标签

    表单简介 Form表单主要用于用户与Web应用程序进行数据的交互,它允许用户将数据发给web应用程序,网页也可以拦截数据的发送以便自己使用.form通常由一到多个表单元素组成,这些表单元素是单行/多行 ...

  2. E203译码模块(3)

    下面的代码译码出指令的立即数,不同的指令有不同的立即数编码形式. //I类型指令的imm,[31:20],符号位扩展成32位. wire [31:0] rv32_i_imm = { {20{rv32_ ...

  3. python3调用R语言干货

    R语言知识:https://www.w3cschool.cn/r/r_lists.html 1. 安装库rpy2 1. 下载与本地对应python版本模块,pip install rpy2是安装不上的 ...

  4. 2-3 arrary数组的数值的计算

    In [2]: import numpy as np tang_array=np.array([[1,2,3],[4,5,6]]) tang_array Out[2]: array([[1, 2, 3 ...

  5. 设置VMware中Kali 共享文件夹

    (软件环境: Vmware Workstation 15.5 Pro , Kali Linux2019.3) 1. VMware设置共享目录 2. 安装VMware-Tools 命令: apt-get ...

  6. 笔记14:Docker 部署Python项目

    Docker 部署Python项目 导读: 软件开发最大的麻烦事之一就是环境配置,操作系统设置,各种库和组件的安装.只有它们都正确,软件才能运行.如果从一种操作系统里面运行另一种操作系统,通常我们采取 ...

  7. Python学习笔记6 函数式编程_20170619

    廖雪峰python3学习笔记: # 高阶函数 将函数作为参数传入,这样的函数就是高阶函数(有点像C++的函数指针) def add(x, y): return x+y def mins(x, y): ...

  8. Lambda 表达式应用 权限管理_用户的角色修改

    Lambda 表达式应用 权限管理_用户的角色修改 需求 前台发送用户新的角色列表,后台查询出用户原有的角色列表. 1.获取出需增加的角色列表 => 在新角色列表中,但是不在原角色列表中的角色 ...

  9. 微信小程序特性总结

    一. 小程序不是运行在浏览器中, 所以没有BOM和DOM对象 即console.log(window)和console.log(document)是获取不到任何内容的 二. 小程序特有的额外js成员( ...

  10. 微信小程序入门笔记

    目录的作用: 1. pages目录: 该目录下存放所有的定义页面 2. utils目录: 该目录下存放定义的一些小功能组件 3. 根目录下app.js文件: 定义小程序对象, 执行小程序生命周期内的各 ...