首页 > 其他分享 >【学习笔记】manacher

【学习笔记】manacher

时间:2024-01-25 21:14:17浏览次数:30  
标签:manacher 扩展 笔记 id 学习 长度 mx 特殊字符 回文

众所周知,manacher 又叫“马拉车”算法,是一种线性求解最长回文子串的算法。

推荐结合模板阅读此文。

求最长回文子串,首先想到的是暴力。枚举子串的左右端点 \(l,r\) ,再判断这个子串是否回文。总复杂度 \(O(n^3)\),效率过低。

观察发现,我们可以只枚举中点,然后同时向左右不断扩展,当无法再扩展时更新答案。

具体来说,我们求出所有的 \(P_i\),即以字符 \(S_i\) 为中心的最大扩展长度(包括 \(S_i\) 本身)。一开始,所有 \(P_i\) 设为 \(1\)(单个字符肯定是回文串)。

但这样有一个问题:长度为偶数的回文串没有回文中心。针对这个问题,我们在字符串每个字符间加上一个“特殊字符”,保证这个字符不在原字符串的字符集中(下文取 | 作为这个特殊字符)。

拿样例 aaa 举例,加上特殊字符后整个字符串变成了这个样子:

|a|a|a|

这有什么好处呢?对于回文长度为偶数的子串,回文中心就为一个 |

所以整个串的 \(P\) 数组如下:

S: |  a  |  a  |  a  |
P: 1  2  3  4  3  2  1

可以推出,以 \(S_i\) 为回文中心在新字符串的最长回文串的长度为 \(P_i \times 2 - 1\)。去除特殊字符(显然特殊字符比原字符多一个,所以特殊字符的个数为 \(P_i\)),那么最长回文子串的长度即为 \(P_i - 1\)。

所以答案为 \(\max\{P_i - 1\}\)。

#include<bits/stdc++.h>
using namespace std; 
const int N = 11e6 + 9;
char s[N << 1];
int p[N << 1],len,ans;
void read(){//读入字符串
	char c = getchar();len = 1;
	s[len] = '|';
	while(c < 'a' || c > 'z')
		c = getchar();
	while(c >= 'a' && c <= 'z'){
		s[++len] = c;
		s[++len] = '|';//添加特殊字符
		c = getchar();
	}
}
int main(){
	read();
	for(int i = 1,mx = 0,id = 0;i <= len;i++){
		p[i] = 1;
		while(s[i - p[i]] == s[i + p[i]])//暴力扩展
			p[i]++;
	    ans = max(ans,p[i] - 1);//更新答案
	}
	printf("%d",ans);
	return 0;
}

但是这样还不是最终的 manacher 算法,我们如果想要得到一个线性算法,就要想如何线性求出 \(P_i\)。

我们记在分别以前 \(i\) 个字符为回文中心扩展后访问到的最右侧下标为 \(mx\),即 \(mx = \max\{i + P_i - 1\}\)(以 \(i\) 为扩展中心扩展的长度为 \(P_i\),那么能访问到的最右侧就是 \(i + P_i - 1\)),同时记取到最大值的 \(i\) 为 \(id\)(如果有多个取最右边的一个)。那么在扩展下一个字符 \(s_{i + 1}\) 时,如果 \(i + 1 \leq mx\),则 \(s_{i + 1}\) 一定被包含在以 \(id\) 为中心的最长回文串中,那么 \(s_{i + 1}\) 一定在这个回文串中有对称的字符。

接下来我们推一下以 \(id\) 为对称中心,\(i\)(不是 \(i + 1\))的对称点的下标:

设这个下标为 \(j\),则 \(j\) 到 \(id\) 的距离和 \(id\) 到 \(i\) 的距离应该相等,即 \(id - j + 1 = i - id + 1\),整理即得 \(j = 2 \times id - i\)。

所以如果对称点 \(j\) 有一些回文串包含在以 \(id\) 为回文中心的最长回文串中,则其中最大者的长度为 \(\min\{P_j,\text{以} id \text{为对称中心的最长回文串的} \large{\text{左边界}} \text{\normalsize{到}} j \text{\normalsize{的长度}}\}\)(记为 \(len\)),易知 \(len\) 等于 \(i\) 到这个回文串右边界 \(mx\) 的长度(由对称可知),所以 \(len = \min\{P_j,mx - i + 1\}\)这是为了保证以 \(j\) 为回文中心的这个串一定在以 \(id\) 为中心的最长回文串中,这样对称过去才能保证以 \(i\) 为回文中心,上面这个数值扩展长度一定是一个回文串。

由上述我们可以得到如果 \(i\) 包含在以 \(id\) 为中心的最长回文串中,则 \(P_i\) 至少为 \(\min\{P_j,mx - i + 1\}\),我们再以这个基础暴力扩展。否则,我们就只能老老实实的从 \(P_i = 1\) 开始扩展。

扩展完成之后,我们再按 \(mx\) 和 \(id\) 的定义更新 \(mx\) 和 \(id\) 即可。

总复杂度是线性的(不想证了)。

#include<bits/stdc++.h>
using namespace std; 
const int N = 11e6 + 9;
char s[N << 1];
int p[N << 1],len,ans;
void read(){
	char c = getchar();len = 1;
    s[0] = '~';s[len] = '|';
	while(c < 'a' || c > 'z')
		c = getchar();
	while(c >= 'a' && c <= 'z'){
		s[++len] = c;
		s[++len] = '|';//添加特殊字符
		c = getchar();
	}
}
int main(){
	read();
	for(int i = 1,mx = 0,id = 0;i <= len;i++){
		p[i] = i <= mx ? min(p[(id << 1) - i/*i关于id的对称点*/],mx - i + 1) : 1;
		while(s[i - p[i]] == s[i + p[i]])//暴力扩展
			++p[i];
	    if(p[i] + i - 1 >= mx){//访问到的最右侧的下标大于mx,更新mx和id
			mx = p[i] + i - 1;
			id = i;
		}
	    ans = max(ans,p[i] - 1);//更新答案
	}
	printf("%d",ans);
	return 0;
}

标签:manacher,扩展,笔记,id,学习,长度,mx,特殊字符,回文
From: https://www.cnblogs.com/5002-qwq/p/17988149

相关文章

  • sql自学笔记(三)常见函数
    select函数名(实参列表)from表concat拼接将姓变大写,名变小写,然后拼接>selectconcat(upper(last_name),lower(first_name))姓名fromemployeessubstr/substring截取子串selectsubstr('我爱上你',2)截取从指定索引处后面所有字符,sql索引从1开始selectsubs......
  • Java学习日记 Day11
    Maven:把maven课程速通了,比较简单,其实就是对工程框架的一个配置,可以用一个总pom文件让整个工程的版本得到确定。SpringMVC:是Servlet的plus版,今天开了个头,明天继续学。算法:①二叉树的所有路径:递归加回溯,用一个List储存结果,一个双向队列储存路径。如果没遇到叶子节点就继续向里递......
  • 2024/1/25学习进度笔记
    平方误差代价函数线性回归有一个训练集,我们选择了线性回归,那么要如何选择合适的参量使得我们的预测更为准确呢??我们知道了现有的数据是准确的,那么预测就要以现有的数据为根基,尽量的贴合现有的数据,使得差距最小,怎么衡量这个差距呢???我们把  平均平方和误差为了方便,我们又......
  • 《人月神话》阅读笔记1
    关于《构建之法》的相关书籍我选择了这本《人月神话》,主要原因是另一本资源下载完没有解压密码,故换此书,第一次读书笔记主要讲讲前五章的内容和感受焦油坑  大多团队的系统在构建过程中容易被大量简单问题所编织的焦油坑所拖累,难以看清问题本质,因此想要解决问题就要先理解问题。......
  • Trie树学习笔记+杂题(进阶1 Trie)
    前言:回来上课吧,不然真的就没人了。现在也是没有脑子一、Trie树学习笔记+杂题(进阶1Trie)相关题单:戳我1.trie树简介字典树,英文名trie。顾名思义,就是一个像字典一样的树,核心原理就是用空间换时间,利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,所以它可以处......
  • Trie树学习笔记+杂题(进阶1 Trie)
    前言:回来上课吧,不然真的就没人了。现在也是没有脑子一、Trie树学习笔记+杂题(进阶1Trie)相关题单:戳我1.trie树简介字典树,英文名trie。顾名思义,就是一个像字典一样的树,核心原理就是用空间换时间,利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,所以它可以处......
  • 微雪ESP32-S3-Zreo学习笔记之WS2812
    板载WS2812板载1颗WS2812连接IO21软件下载ESP32-S3-Zero没有板载USB转串口,无法实现自动下载。下载软件时要按住Boot按键再上电,此时电脑会识别到一个USB模拟的COM口,可用于下载软件。开发环境编程环境是使用的esp-idf-4.4.2;安装esp-idf-5.0.2、esp-idf-5.1.2都不能正常使用......
  • OpenMP学习 第十章 超越通用核心的多线程
    第十章超越通用核心的多线程基于通用核心的附加子句并行构造的附加子句:num_threads(integer-expression)用于设置线程总数.if(scalar-expression)用于为并行构造提供条件分支.copyin(list)proc_bind(master|close|spread)为了测试num_threads子句与if子句的用法,......
  • 层次分析法笔记【零基础数模系列】
    前言看了很多讲解新概念新模型的文章,这些文章往往要么讲的很浅不讲原理只讲应用,让人知其然不知其所以然。要么讲的很深小白看不懂,同时总是忽略关键部分,经常性引入陌生概念让初学者疑惑,因此有了本文,任何能熟练掌握线性代数知识且逻辑思维能力尚可的人都可以理解,而无需其他数模知识......
  • 李宏毅《机器学习》总结 - CNN
    使用场景:对图片进行分类首先,将图片变成向量。例如,对于一个彩色的\(N\timesN\)(这个N指的是像素个数)图片,其对应着一个\(N\timesN\times3\)的矩阵(其中3是图片的channel,在彩色图片中,每个像素由RGB构成,因此channel为3)一个初始的想法将这个矩阵拉长,变成一个向量,然后......