P3805 【模板】manacher - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
“没时间悼念KMP了,接下来上场的是Manacher!”
什么是Manacher?
历史背景:
1975 年,一个叫 Manacher 的人发明了这个算法,所以叫Manacher 算法(中文名:马拉车算法)
应用背景:
现在有个字符串A,你需要找到该字符串中最长的回文字串,输出其长度。
回文串是正着读反着读都是一样的,例如:101,aba,tnt。
Manacher算法思路:
Manacher第一步修改原字符串。
我们认定一个字符串是否为回文串,都会先找这个字符串的中心,然后向两边扩散进行字符对比。比如aba这个字符串,我们要判断它是否为回文串,就可以先找到中心位置b,然后再向两边扩散比较,b左边的字符是不是等于b右边的字符,如果都一致,那么该字符串就是回文串。
当然,聪明的你一定发现了一个问题,如果该字符串是abba这种偶数长度的字符串呢?这个时候我们就可以把偶数长度的字符串变为奇数长度的字符串,向原字符串插入一个原字符串不会出现的字符,比如,"$"。那么对于abba而言插入后的字符串就变成了$a$b$b$a$。然后我们就可以套用判断奇字符串是否是回文串的思想,来对这个进行判断了。
插入相同字符后的字符串如果是回文串,那么没有插入相同字符的字符串也是回文串,可自行验证。
所以Mannacher的第一步就是在原来的字符串的基础上,插入一个原字符串不会出现的字符。
Manacher第二步回文范围[L,R],回文中心W。
约定p数组放的是回文半径,则p[i]存放的是以i为回文中心的回文半径。
如图,在字符串(黑色框)中有一回文子串(蓝色框),范围为[L, R],其中M为该回文串的中心,现在我们欲求以i为回文中心的回文半径r。就要先分两种大情况。
第一种是i在[L, R]区间里,我们就可以先找i相当于M的对称点K,因为i是在K之后的所以K的回文半径是已知的,那么当K的回文区域不包含L,即K-p[K]+1>L时,p[i] = p[k] =[2*M-i]。当K的回文区域包含L时,即K-p[K]+1<=L时,p[i] = R-i+1。
第二种是i在[L, R]区间外面的,我们就只能暴力向两边扩散比较,求得它的回文半径。
经过如图的数学推导,我们就可以给第一种情况化简为p[i]=min(p[2*M-i], R-i+1),然后再向两边扩散比较字符。
通过以上这种方法,写一个for循环加一层while就能够快捷地得到以每个字符作为回文中心的回文半径了。
得到回文半径有什么用呢?题目不是让我们求字符串里面最长的回文子串有多长嘛?那么最大的回文半径-1,不就是最长的回文子串的长度嘛?也就解开了。
因为原字符串在插入了原字符串没有的字符后,原字符串的长度变成了原来的2倍,所以回文半径-1就是最长的回文子串。可自己写几个试试。
【算法详解】Manacher算法_哔哩哔哩_bilibili
如何实现Manacher代码?
插入原字符串没有的字符
for (ll i = 0; i < 2 * a.length(); i++)
{
if (i & 1)
//如果是奇数
b[i] = a[i / 2];
else
b[i] = '$';
}
b[2 * a.length()] = '$';
//a为原字符串
//b为插入后的字符串
求以每个字符作为回文中心的回文半径
ll M = 0, R = 0;
//初始化M和R
for (ll i = 0; i <= 2 * a.length(); i++)
{
// for循环遍历字符串里面每个字符
if (i > R)
p[i] = 1;
//如果i是在[L, R]区间外面的
//那么它的回文半径默认从1开始
else
p[i] = min(p[2 * M - i], R - i + 1);
//否则回文半径就是min(p[2 * M - i], R - i + 1)
while (i + p[i]<= 2 * a.length() && i - p[i] >= 0 && b[i - p[i]] == b[i + p[i]])
//while循环就是向两边扩散比较的过程
p[i]++;
//只有当左右两边的字符一致
//回文半径才+1
if (i + p[i] - 1 > R)
{
M = i;
R = i + p[i] - 1;
}
//如果以新的字符为回文中心
//回文半径更大的话
//那么就以新的字符为回文中心
//更新R
}
全部代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N = 2e7;
string a;
vector<ll> p(2 * N);
char b[2 * N];
int main()
{
cin >> a;
for (ll i = 0; i < 2 * a.length(); i++)
{
if (i & 1)
b[i] = a[i / 2];
else
b[i] = '$';
}
b[2 * a.length()] = '$';
// cout << "变形后的:" << b << endl;
ll M = 0, R = 0;
for (ll i = 0; i <= 2 * a.length(); i++)
{
if (i > R)
p[i] = 1;
else
p[i] = min(p[2 * M - i], R - i + 1);
while (i + p[i]<= 2 * a.length() && i - p[i] >= 0 && b[i - p[i]] == b[i + p[i]])
p[i]++;
if (i + p[i] - 1 > R)
{
M = i;
R = i + p[i] - 1;
}
}
// cout << M << " " << R << endl;
cout << *max_element(p.begin(), p.end()) - 1;
return 0;
}