首页 > 编程语言 >「笔记」manacher 算法

「笔记」manacher 算法

时间:2023-01-11 19:11:06浏览次数:55  
标签:子串 中心 manacher 扩展 pos 笔记 算法 回文

目录

写在前面

才发现好久没写知识笔记了……

神兵小将真好看,感觉好像年轻了十岁,有一种莫名的沉浸式的体验。

还记得当年特别喜欢那个粉毛来、、、小学生特有的性冲动、、、

简介

暴力 \(O(n^2)\) 求回文子串方法非常简单,枚举回文子串中心向两侧扩展即可,注意特判偶数长度的回文子串。

而 manacher 算法在暴力的基础上,利用了已求得的回文半径加速了比较的过程,使算法可以时空间复杂度均为 \(O(n)\) 级别下完成。

算法流程

以下以模板题:P3805 【模板】manacher 算法 为例。

给定一长度为 \(n\) 的只由小写英文字符构成的字符串 \(s\),求 \(s\) 中最长的回文子串的长度。
\(1\le n\le 1.1\times 10^7\)。
500ms,512MB。

首先在原串的开头、末尾和相邻字符间加入分隔符,使得串长度变为 \(2\times n + 1\)。原串和新串中的回文子串均一一对应,且新串中的回文子串都是有中心奇数长度的串。

考虑在枚举回文子串中心 \(i\) 时维护一个数组 \(p\),\(p_i\) 表示以 \(s_i\) 为中心的最长回文子串的半径长度。即有:

\[p_i = \max(\{x | x\in \N^+, \forall j < x, s_{i - j} = s_{i + j}\}) \]

同时维护两个变量 \(pos\) 和 \(r\)。\(r\) 代表以某个位置为中心能扩展到的最靠后的位置,\(pos\) 代表上述的位置,则显然有 \(r=pos + p_{pos}-1\)。显然,对于当前枚举到的回文子串中心 \(i\),由于 \(p_i\ge 1\),则更新 \(p_{i-1}\) 后至少有 \(r = i-1\),则有 \(i\in (pos, r+1]\) 成立。

同时,我们记 \(l=pos - p_{pos}+1\) 代表以 \(pos\) 为中心能扩展到的最靠前的位置。显然,由于 \([l,r]\) 是一个回文串,由对称性,则对于以 \(i\) 为中心的某些回文子串,在 \((l, pos)\) 中一定存在一个 \(j\),满足 \(i + j = 2\times pos\),且以 \(j\) 为中心的某些回文子串与以 \(i\) 为中心的某些回文子串完全相同。如下图所示:

图 1

来源:https://www.luogu.com.cn/blog/Minamoto/solution-p3805

显然,如果我们在计算以 \(i\) 为中心的最长回文子串时,如果可以利用 \(j\) 的信息 \(p_j\),即可避免大量无用的扩展过程。我们考虑 \(p_j\) 的取值对 \(p_i\) 的影响:

  1. 如果以 \(j\) 为中心的最长回文子串的左端点不会越过 \(l\),即有:\(j - p_j + 1 \ge l\),则 \(p_{i} = p_j\),如下图所示。
还是上面的图 1

来源:https://www.luogu.com.cn/blog/Minamoto/solution-p3805

  1. 如果以 \(j\) 为中心的最长回文子串左端点越过了 \(l\),即有:\(j - p_j + 1 < l\),则 \(p_{i} \ge r - i + 1\),如下图所示。
图 2

来源:https://www.luogu.com.cn/blog/Minamoto/solution-p3805

这时我们仅需从第 \(r-i+1\) 位开始以 \(i\) 为中心仅需扩展即可。

再考虑何时应当更新 \(pos\) 的值。我们令 \(pos\) 的初始值为 1,在枚举 \(i\) 过程中,每当计算出一个新的 \(p_i\),就将 \(r' = i + p_i - 1\) 与当前的 \(r\) 进行比较,如果 \(r'>r\),则令 \(pos=i, r = r'\) 即可。

注意求得所有 \(p_i\) 后将其转化为原串的回文串长度。显然,对于以新串中位置 \(i\) 为中心的最长回文子串,对应原串中对应位置长度为 \(p_i - 1\) 的最长回文子串。

代码如下。为了简便记忆,代码中并没有将上述情况 1、2 分开编写,而是均是采用了给 \(p_i\) 赋初始值后再尝试扩展的写法,显然正确性不受影响。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
const int kN = 1e7 + 1e6 + 10;
//=============================================================
int n, p[kN << 1];
char s[kN], t[kN << 1];
//=============================================================
inline int read() {
	int f = 1, w = 0; char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
	for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
	return f * w;
}
//=============================================================
int main() {
	scanf("%s", s + 1); n = strlen(s + 1);
	for (int i = 1; i <= n; ++ i) t[2 * i - 1] = '%', t[2 * i] = s[i];
	t[n = 2 * n + 1] = '%';
	int pos = 0, r = 0;
	for (int i = 1; i <= n; ++ i) {
		p[i] = 1;
		if (i < r) p[i] = std::min(p[2 * pos - i], r - i + 1);
		while (i - p[i] >= 1 && i + p[i] <= n && 
					 t[i - p[i]] == t[i + p[i]]) {
			++ p[i];
		}
		if (i + p[i] - 1 > r) pos = i, r = i + p[i] - 1;
	}
	int maxp = 0;
	for (int i = 1; i <= n; ++ i) maxp = std::max(maxp, p[i] - 1);
	printf("%d\n", maxp);
	return 0;
}

复杂度证明

考虑暴力扩展的过程。发现暴力扩展仅会发生在 \(i>r\),或是 \(j<l\) 时,且都是从 \(i+1\) 开始扩展。

  • 当 \(i>r\) 时,暴力扩展后必有 \(i+p_i-1>r\),则必定引起 \(r\) 的右移,右移次数不小于 \(p_i\),即暴力扩展次数 \(-1\)。
  • 当 \(j<l\) 时,如果暴力扩展成功,则一定会引起 \(r\) 的右移。右移次数同样等于暴力扩展次数 \(-1\);如果不成功,则 \(r\) 不变。

而 \(r\) 至多仅会更新 \(n\) 次,暴力扩展次数与之同阶也为 \(O(n)\) 级别。同样地,\(i\) 仅会右移 \(n\) 次,则算法的总复杂度为 \(O(n)\) 级别。

写在最后

学完发现这东西好水。

为什么之前不学?因为我觉得 \(O(n\log n)\) 的哈希+二分也挺好/cy

标签:子串,中心,manacher,扩展,pos,笔记,算法,回文
From: https://www.cnblogs.com/luckyblock/p/17044694.html

相关文章

  • 算法学习笔记(53)——排序不等式
    排序不等式题目链接:AcWing913.排队打水让最磨叽的人最后打水。如图所示,第一个同学被等了6次,第二个同学被等了5次,以此类推...\[总时间=t_1\times(n-1)+t_2\t......
  • Python学习笔记-常用模块介绍--时间模块
    1.时间模块分为哪三种格式?1.时间戳2.格式化字符串3.结构化时间 2.时间的示例#1.时间戳---常用于运算的print(time.time())#2.格式化字符串---用于显示,方......
  • Python学习笔记-常用模块介绍--猴子补丁
    1.什么是猴子补丁?属性在运行时的动态替换,叫做猴子补丁(MonkeyPatch)【发音ˈmʌŋkipætʃ】是一种思想,应用于团队引用了公共模块,想丰富模块,就在入口打这种“猴子补......
  • 算法入门(第二天)---双指针977,189
    977.有序数组的平方给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。输入:nums=[-4,-1,0,3,10]输出:[0,1,......
  • laravel使用JWT签名算法,HS256和RS256有什么区别
    JWT签名算法中HS256和RS256有什么区别JWT签名算法中,一般有两个选择,一个采用HS256,另外一个就是采用RS256。签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接......
  • 算法学习笔记(52)——Huffman树
    Huffman树题目链接:AcWing148.合并果子利用贪心的思想,每次从当前所有堆中,挑出最小的两堆合并即可。#include<iostream>#include<algorithm>#include<queue>usi......
  • Docker学习笔记:查看当前用户和密码
    一、查看查看所有docker服务器登陆的用户和密码。#Linuxcat/root/.docker/config.json#WindowsC:\Users\Name\.docker\config.json二、密码解码在config.js......
  • fabric学习笔记3
    fabric学习笔记320201303张奕博2023.1.11HyperledgerFabric架构设计分布式帐本区块链核心概念是分布式帐本,就像下面的图1所示,同样的帐本(全量的交易数据,详见下节)在......
  • 算法学习笔记(51)——区间问题
    区间问题区间问题1.区间选点2.最大不相交区间数量3.区间分组4.区间覆盖区间选点题目链接:AcWing905.区间选点题目描述给定\(N\)个闭区间\([a_i,b_i......
  • Halcon学习笔记之支持向量机(二)
    在Halcon中模式匹配最成熟最常用的方式该署支持向量机了,在本例程中展示了使用支持向量机对卤素灯的质量检测方法。通过这个案例,相信大家可以对支持向量机的使用有一个更加......