Manacher 学习笔记(持续更新中……)
结合模板题P3805 【模板】Manacher讲解。
概念
Manacher 算法用于解决最长回文子串问题,时间复杂度是简洁的 \(O(n)\)。
思路
四个字,插扩借推。
插
回文串分两种,奇数和偶数。
但是 Manacher 算法比较暴力而又简介,于是它在每个字符之间都加上了 #(一般都是),使得所有回文串都被迫变成了奇数回文。
比如字符串 abba,它变成了 #a#b#b#a#。
从上面这个例子我们知道两头也要加。
扩
都知道求这种问题的最暴力思路是 \(O(n^2)\) 列举字串并 \(O(n)\) 判断是否回文,一共是 \(n^3\) 的复杂度。
但是还有一种 \(O(n^2)\) 的方法叫做中心扩张,就是说回文串不管是奇是偶总该有个对称中心,列举对称中心再一个一个往外扩张直到碰壁为止。
借
维护一个“当前已知最靠右的回文串”的中心 C 和右边界 R。当计算的新中心 i 落在 R 之内时,利用回文对称性,i 的初始半径可以直接“借用”对称点 2C-i 的半径(取它与到 R 距离的较小值),从而避免从头扩展。
推
每当完成一次中心扩展后,若新回文的右边界超过了当前的 R,就更新 C 和 R,把“最右边界”不断向右推进。这使得后续的“借”能覆盖更多位置,保证整体比较次数为线性。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,ans,cnt,r[22000005];
char s1[11000005],s[22000005];
signed main()
{
scanf("%s",s1+1);
cnt=strlen(s1+1);
s[++n]='~';
for(int i=1;i<=cnt;++i)
{
s[++n]=s1[i];
s[++n]='~';
}
for(int i=1;i<=n;++i)
{
if(i<=m+r[m]-1) r[i]=min(r[2*m-i],m+r[m]-i);
else r[i]=1;
while(i+r[i]<=n&&i-r[i]>=1)
{
if(s[i+r[i]]!=s[i-r[i]]) break;
++r[i];
}
if(i+r[i]>m+r[m]) m=i;
ans=max(ans,r[i]-1);
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号