Manacher 算法学习小记
概要
一个字符串有多少个回文的字串?最多有 \(O(n^2)\) 级别个。但 Manacher 算法却可以用 \(O(n)\) 的时间复杂度解决这个问题。同时 Manacher 算法实现非常简单。
一个显而易见的结论是:如果 \(S_{1\cdots n}\) 是回文串,那么 \(S_{2\cdots n-1}\) 也是回文串。
根据这一个性质,我们可以得到 \(O(n^2)\) 的暴力的做法:
以 \(i\) 为中心,向两侧暴力扩展,得到所有以 \(i\) 位中心的回文串。这些回文串长度为奇数。
以 \(i\) 和 \(i+1\) 为中心,向两侧暴力扩展,得到所有以 \(i\) 和 \(i+1\) 为中心的回文串。这些回文串的长度为偶数。
实际上,为了方便实现,可以在两个字符间和首尾插入空字符。这样所有的回文串的长度都变为奇数。下面默认使用了这种方法,所有回文串长度为奇数,下标从 \(1\) 开始。
Manacher 充分利用了回文的性质,构造出令人惊叹的巧妙做法:
令 \(d_i\) 表示以 \(i\) 为中心的回文串最大长度的一半,那么只要求得 \(d\) ,就可以知道所有回文串的信息。
不妨令 \(l,r\) 表示当前考虑到的回文串中 右端点最靠右 的那个回文串。初始时不妨令 \(l=r=0\) 。
从左到右枚举回文中心 \(i\) 。如果 \(i > r\) ,那么调用暴力算法求得 \(d_i\) 。否则可以找到回文串 \(S_{l\cdots r}\) 中与 \(i\) 对称的位置 \(j=l+(r-i)\) 。此时,如果 \(i+d_j < r\) ,那么根据 \(S_{l\cdots r}\) 的对称性, \(d_i=d_j\) 。否则的话, 由于无法保证 \(r\) 之后与 \(l\) 之前的对称性,先令 \(d_i=r-i\) ,再在此基础上执行暴力算法。
最后不要忘记更新 \(l,r\) 。
不难发现, \(r\) 是不减的。而暴力算法中向两侧暴力拓展的次数不超过 \(r\) 增加的值。所以 Manacher 算法中,暴力的部分均摊是 \(O(n)\) 的。外层循环也是 \(O(n)\) ,那么总的时间复杂度就是 \(O(n)\) 的。
例
\(S\) 长度 \(1e5\) 我线段树一只 \(log\) T 了?这个评测机略微有点快啊……不过可以 \(O(n)\) 的……
在 Manacher 之后,可以 \(O(n)\) 预处理出对于每个位置 \(i\) 为结尾的最长回文串和以 \(i\) 为开始的最长回文串。通过加入的空字符统计答案即可。注意必须要是两个回文串,所以单一一个空字符不能算作回文。
#include <cstdio>
#include <cstring>
#include <algorithm>
const int Maxn = 100010;
const int INF = 1e9;
char Ch[Maxn << 1];
int D[Maxn << 1];
int n, Ans, L[Maxn << 1], R[Maxn << 1];
inline void Manacher();
int main() {
scanf("%s", Ch + 1);
n = strlen(Ch + 1);
for (int i = n; i >= 1; --i) Ch[i << 1] = Ch[i];
for (int i = 0; i <= n; ++i) Ch[i << 1 | 1] = '_';
n = n << 1 | 1;
Ch[0] = '*', Ch[n + 1] = '\0';
Manacher();
for (int i = 1; i <= n; ++i) {
L[i + D[i]] = std::max(L[i + D[i]], D[i]);
R[i - D[i]] = std::max(R[i - D[i]], D[i]);
}
for (int i = 1; i <= n; ++i)
if (i & 1)
R[i] = std::max(R[i], R[i - 2] - 2);
for (int i = n; i >= 1; --i)
if (i & 1)
L[i] = std::max(L[i], L[i + 2] - 2);
Ans = 0;
for (int i = 1; i <= n; ++i)
if (R[i] && L[i])
Ans = std::max(Ans, R[i] + L[i]);
printf("%d\n", Ans);
return 0;
}
inline void Spand(int x) {
for(; Ch[x - D[x] - 1] == Ch[x + D[x] + 1]; ++D[x]);
return;
}
inline void Manacher() {
int L = 0, R = 0;
for (int i = 1; i <= n; ++i) {
if (i > R) {
Spand(i);
L = i - D[i], R = i + D[i];
continue;
}
int Ops = L + (R - i);
if (i + D[Ops] >= R) {
D[i] = R - i;
Spand(i);
L = i - D[i], R = i + D[i];
continue;
}
D[i] = D[Ops];
}
return;
}
Manacher 算法学习小记的更多相关文章
- Manacher算法学习笔记 | LeetCode#5
Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...
- Gcd&Exgcd算法学习小记
Preface 对于许多数论问题,都需要涉及到Gcd,求解Gcd,常常使用欧几里得算法,以前也只是背下来,没有真正了解并证明过. 对于许多求解问题,可以列出贝祖方程:ax+by=Gcd(a,b),用E ...
- Manacher算法学习 【马拉车】
好久没写算法学习博客了 比较懒,一直在刷水题 今天学一个用于回文串计算问题manacher算法[马拉车] 回文串 回文串:指的是以字符串中心为轴,两边字符关于该轴对称的字符串 ——例如abaaba 最 ...
- manacher算法学习(求最长回文子串长度)
Manacher总结 我的代码 学习:yyb luogu题目模板 xzy的模板 #include<iostream> #include<cstdlib> #include< ...
- Manacher算法学习笔记
前言 Manacher(也叫马拉车)是一种用于在线性时间内找出字符串中最长回文子串的算法 算法 一般的查找回文串的算法是枚举中心,然后往两侧拓展,看最多拓展出多远.最坏情况下$O(n^2)$ 然而Ma ...
- Manacher 算法学习笔记
算法用处: 解决最长回文子串的问题(朴素型). 算法复杂度 我们不妨先看看其他暴力解法的复杂度: \(O(n^3)\) 枚举子串的左右边界,然后再暴力判断是否回文,对答案取 \(max\) . \(O ...
- Cipolla算法学习小记
转自:http://blog.csdn.net/doyouseeman/article/details/52033204 简介 Cipolla算法是解决二次剩余强有力的工具,一个脑洞大开的算法. 认真 ...
- 学习笔记 - Manacher算法
Manacher算法 - 学习笔记 是从最近Codeforces的一场比赛了解到这个算法的~ 非常新奇,毕竟是第一次听说 \(O(n)\) 的回文串算法 我在 vjudge 上开了一个[练习],有兴趣 ...
- 二次剩余Cipolla算法学习笔记
对于同余式 \[x^2 \equiv n \pmod p\] 若对于给定的\(n, P\),存在\(x\)满足上面的式子,则乘\(n\)在模\(p\)意义下是二次剩余,否则为非二次剩余 我们需要计算的 ...
随机推荐
- ListSetAndMap
package com.collection.test; import java.util.ArrayList; import java.util.HashMap; import java.util. ...
- 关于使用jquery评论插件...
.今天做项目,使用了一个评论插件 调用出来没事, 可是添加的时候报错 Uncaught TypeError: $(...).find(...).live is not a function 这个错误 ...
- Linux系统上对其他用户隐藏进程的简单方法
mount -o remount,rw,hidepid=2 /proc 我使用的是多用户系统,大部分的用户通过ssh客户端访问他们的资源.我如何(怎么样)避免泄露进程信息给他们?如何(怎么样)在Deb ...
- macos下简单的socket服务器+客户端
TCP客户端服务器编程模型: 服务器: 调用socket函数创建套接字 调用bind绑定本地IP和端口 调用listen启动监听(准备好接收客户端链接的队列) 调用accept从已连接队列中提取第一个 ...
- SQLite3学习笔记(1)
命令: DDL-数据定义: CREATE -- 创建一个新的表,一个表的视图,或者数据库中的其他对象 ALTER -- 修改数据库中的某个已有的数据对象,比如一个表 DROP -- 删除整个表,或者表 ...
- 拆机联想ideapad s500
这是我第一次拆机,中间也是经历了各种艰难险阻,最后还算是成功.首先,说一下拆机得目的:很简单,为了加一个内存条:下面具体说拆机得步骤: 第一步,在网上查攻略,刚开始的时候,并没有很详细的具体到机型,只 ...
- 【CQOI2017】老C的方块
Description https://loj.ac/problem/3022 Solution 他讲得很清楚 将那篇博客中的红色标号为 \(0\),黄色为 \(1\),蓝色为 \(2\),绿色为 \ ...
- C++获取文件夹下所有文件的路径
代码 getFiles()函数的作用: path是一个文件夹路径,函数在path文件夹下寻找所有文件(包括子文件夹下的文件),然后将所有文件的路径存入files #include <io.h&g ...
- tomcat 配置https协议
开发的人脸识别功能,在本地localhost是可以访问,换成IP地址不能访问,通过不了浏览器的安全协议, 要把http协议,转成https协议,才能正常访问 方案有二种 1.在项目springboot ...
- C#中怎么将XML作为参数post到接口
String xml = "<data>中文</data>"; String postData = "data=" + Server.U ...