# **Manacher算法**
## **Manacher算法概述**
Manacher算法是用来解决字符串问题中经典的最长回文子串问题的。对于长度为n的字符串,
计算出该字符串的最长回文子串的长度,我们可以直接想到的有两种算法。
(1)枚举左端点和右端点L和R,每次判断[L,R]是否为回文,这样的复杂度为O(n^3)
(2)枚举对称轴mid,向左右扩展,更新最大值,复杂度为O(n^2)
而Manacher算法可以在线性时间内来解决这个问题。
## **Manacher算法过程**
### **step1.预处理**
由于对于给定的字符串的长度奇偶不确定,因此我们对一个给定的字符串,首位以及每两个字符之间插入一个从来没有出现过的字符比如'*'。假设给定的字符串为
```
aba
```
那么经过处理之后的字符串就变成了
```
*a*b*a*
```
### **step2.辅助数组**
我们定义p[i]为以i为中心,最长的回文半径的长度。对于上面的
```
*a*b*a*
```
其对应的p数组为
```
1 2 1 4 1 2 1
```
不难发现,我们所要求的最长回文字串 的长度即为max(p[i]-1),那么我们接下来的任务就是在线性的时间内求解出数组p。
### **step3.辅助数组的求解**
求解p数组的时候我们需要两个变量:pos和maxxright
pos代表当前能延伸到最右端的回文字串的中心位置。
maxxright为能延伸到的最右端,显然maxxright=pos+p[pos]-1
上面的两个变量是已知的,那么我们如何求解p[i]呢?
对于i来说,它一定是大于pos的。那么我们只需要判断i和maxxright的关系即可。
也就是两种情况:
第一种:maxxright>i
这种情况下我们可以找到i关于pos对称的位置j,那么我们可以直接先让p[i]=p[j] (这个不用证明,看图就懂了)
![](https://box.kancloud.cn/76772d236de9d358834fd6ff242a486b_652x23.png)
但是如果p[j]太大呢?
那么p[i]就要对maxxrigth-i取个最小值才可以。
![](https://box.kancloud.cn/4753870467e980d3ba42675ab57d8d3b_937x39.png)
第二种:i<=maxxright
此时直接让p[i]=1,即可。
但是这样还没有算完p[i],我们仍需要进行两边扩展,直到s[i-p[i]]!=s[i+p[i]]
如果计算出来的i+p[i]-1大于当前的maxxright,maxxright和pos就要进行更新了。
这样经过了p数组的优化,每个字符串扩展比较的次数是很少的。最终的复杂度为O(n)
## **code**
```
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
int p[N];
string s;
int manacher(string s){
string t="";
t+='*';
for(int i=0;i<(int)s.size();i++){
t+=s[i];
t+='*';
}
int ans=0;
int pos=0;int maxxright=0;
for(int i=0;i<(int)t.length();i++){
p[i]=maxxright>i?min(p[2*pos-i],maxxright-i):1;//关键
while(i-p[i]>=0&&i+p[i]<(int)t.length()&&t[i-p[i]]==t[i+p[i]]) p[i]++;
if(i+p[i]-1>maxxright){
maxxright=i+p[i]-1;
pos=i;
}
ans=max(ans,p[i]);
}
return ans-1;
}
int main(){
cin>>s;
cout<<manacher(s)<<endl;
return 0;
}
```
## **例题**
[**吉哥系列故事——完美队形II**](http://acm.hdu.edu.cn/showproblem.php?pid=4513)
这个题是让求满足从左到对称轴单调递增的最长回文字串的长度,其实就是在扩展的过程中加一个限制条件即可。
还有就是这道题的预处理插入的是一个从未出现的数字。
```
~~~
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+100;
vector<int> p,s;
int LR[N];
int manacher(vector<int> &s){
p.clear();
memset(LR,0,sizeof(LR));
int maxx=0;
int pos=0;
int ans=0;
int l=(int)s.size();
p.push_back(300);
for(int i=0;i<l;i++){
p.push_back(s[i]);
p.push_back(300);
}
int SIZ=(int)p.size();
for(int i=0;i<SIZ;i++){
if(i<maxx) LR[i]=min(LR[2*pos-i],maxx-i);
else LR[i]=1;
int now;
if(p[i]==300) now=p[i-LR[i]];
else now=p[i];
while(i-LR[i]>=0&&i+LR[i]<SIZ&&p[i-LR[i]]==p[i+LR[i]]){
if(p[i-LR[i]]==300||p[i-LR[i]]<=now){
LR[i]++;
now=min(now,p[i-LR[i]]);
}
else break;
}
if(i+LR[i]-1>maxx){
maxx=i+LR[i]-1;
pos=i;
}
ans=max(ans,LR[i]);
}
return ans-1;
}
int main(){
int T,n,x;
scanf("%d",&T);
while(T--){
s.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&x),s.push_back(x);
printf("%d\n",manacher(s));
}
return 0;
}
~~~
```
- 简介
- 零基础快速入门ACM
- C语言快速入门
- C语言快速入门
- C/C++基础
- 输入输出
- 数组和字符串
- 数组
- 字符数组
- 函数和递归
- C++标准容器库(STL)
- Vector
- Map
- Set
- String
- Stack
- Queue
- Priority_queue
- 贪心
- 硬币问题
- 区间调度问题
- 字典序最小问题
- 独木舟问题
- 搜索
- DFS
- BFS
- 剪枝
- 记忆化搜索
- 常用技巧
- 倍增
- 高精度
- 大数加法
- 大数减法
- 大数乘法
- 大数除法
- 大数阶乘
- 输入挂
- 前缀和
- 二分
- 本地预处理
- 尺取
- 枚举
- 坐标离散化
- 分治
- 数列分治
- 树上分治
- 平面分治
- 计算几何
- 凸包
- 点积
- 叉积
- 线段关系
- 皮克定理
- 最近点对
- 数据结构
- 线段树
- 树状数组
- 并查集
- 动态规划
- 基础知识
- 信息学竞赛中的动态规划
- 动态规划初步
- 最长上升子序列(LIS)
- 最长公共子序列(LCS)
- 最大子段和
- 背包问题
- 部分背包
- 0 1 背包
- 完全背包
- 多重背包
- 背包的可行性问题
- 线性DP
- 树形DP
- 区间DP
- 数位DP
- 动态规划优化
- 字符串
- KMP算法
- 拓展KMP
- 字符串Hash
- Manacher算法
- 后缀数组
- Trie树
- 博弈论
- Nim博弈
- Bash博弈
- 斐波那契博弈
- 威佐夫博弈
- SG函数
- 数论
- 整数研究
- 素数判断
- 素数筛选
- 快速幂
- 算数基本定理
- 最大公约数(Gcd)
- 费马小定理
- 扩展欧几里得
- 逆元
- 同余定理
- 组合数学
- 容斥原理
- 抽屉原理
- 卢卡斯(Lucas)
- 卡特兰(Catalan)
- 著名函数
- 欧拉函数
- 莫比乌斯函数
- 线性代数
- 矩阵运算
- 矩阵快速幂
- 图论
- 存图方法
- 邻接矩阵
- 邻接表
- Vector存图
- 链式前向星
- 图的遍历
- 拓扑排序
- 最短路
- Dijkstra算法
- SPFA算法
- Floyed 算法
- 最小生成树
- Prim算法
- Kruskal算法
- 最近公共祖先 (LCA)
- 二分图匹配
- 网络流
- 其他
- 莫队