【模板】manacher算法
题目描述
给出一个只由小写英文字符 a , b , c , … y , z \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt z a,b,c,…y,z 组成的字符串 S S S,求 S S S中最长回文串的长度 。
字符串长度为 n n n。
输入格式
一行小写英文字符 a , b , c , … y , z \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt z a,b,c,…y,z组成的字符串 S S S。
输出格式
一个整数表示答案。
输入输出样例
输入 #1
aaa
输出 #1
3
说明/提示
1 ≤ n ≤ 1.1 × 1 0 7 1\le n\le 1.1\times 10^7 1≤n≤1.1×107。
思路
为了在接下来的测试里不被字符串坑,所以不得不来学一下Manacher算法。
Manacher算法可以做到在 O ( n ) O(n) O(n)的时间复杂度内实现最大回文子串的查找。
在Manacher算法中,首先要在原字符串的头、尾以及每两个字符的中间插入一个特殊字符。
例如,字符串
S
S
S:
a
,
b
,
c
,
b
,
a
,
d
,
a
\texttt a,\texttt b,\texttt c,\texttt b,\texttt a,\texttt d,\texttt a
a,b,c,b,a,d,a
我们可以按照上述方法,构造出字符串
S
′
S'
S′:
*
,
a
,
*
,
b
,
*
,
c
,
*
,
b
,
*
,
a
,
*
,
d
,
*
,
a
,
*
,
\texttt *,\texttt a,\texttt *,\texttt b,\texttt *,\texttt c,\texttt *,\texttt b,\texttt *,\texttt a,\texttt *,\texttt d,\texttt *,\texttt a,\texttt *,
*,a,*,b,*,c,*,b,*,a,*,d,*,a,*,
这样,我们就把所有的回文子串都变成了奇回文(中点坐标是整数的回文子串)。
我们设三个变量 r , m , m a x n r,m,maxn r,m,maxn,分别表示目前查到的最大回文子串的右端点,目前查到的最大回文子串的中点,目前查到的最大回文子串的半径,再设 p i p_i pi表示以 i i i为中点的最大回文子串的半径。
然后,我们开始扫描整个字符串 S ′ S' S′。
对于每个字符 S ′ i {S'}_i S′i,如果 i > r i>r i>r,那么已知的以 i i i为中点的最大回文子串就只能是 i i i自己,即 p i = 1 p_i=1 pi=1;否则,已知的以 i i i为中点的最大回文子串的半径要么等于 p 2 ∗ m − i p_{2*m-i} p2∗m−i(这意味着以 i i i为中点的最大回文子串中的每个字符 S ′ j {S'}_j S′j,满足 ∀ j ∈ [ m , r ] \forall j \in [m,r] ∀j∈[m,r]且 i ∈ [ m + r 2 , r ] i \in [\frac{m+r}{2},r] i∈[2m+r,r]),要么等于 r − i + 1 r-i+1 r−i+1(这意味着以 i i i为中点的最大回文子串中的每个字符 S ′ j {S'}_j S′j,满足 ∀ j ∈ [ m , r ] \forall j \in [m,r] ∀j∈[m,r]且 i ∈ [ m , m + r 2 ] i \in [m,\frac{m+r}{2}] i∈[m,2m+r]),在这里,我们取两种情况的最小值,即 p i = m i n ( p 2 ∗ m − i , r − i + 1 ) p_i=min(p_{2*m-i},r-i+1) pi=min(p2∗m−i,r−i+1)。
接着,我们对已知的 p i p_i pi,把它向外扩展,看看能不能构造出半径更长的以它为中点的最大回文子串。因为有了上一步的处理,在这一步中,我们不用向外扩展太多就可以找到半径最长的以它为中点的最大回文子串。这就使得算法的效率极大地提高了!
得到了 p i p_i pi的最大值之后,我们就可以更新 r , c , m a x n r,c,maxn r,c,maxn的值了。
最后的答案就是 m a x n − 1 maxn-1 maxn−1。(证明:在字符串 S ′ S' S′中,它的最大回文子串的一半的是 * , a , * , b , * , c , * \texttt *,\texttt a,\texttt *,\texttt b,\texttt *,\texttt c,\texttt * *,a,*,b,*,c,*,显而易见,该子串删去一个 * \texttt * *之后,在剩余的 * \texttt * *的位置填上字母,构造的字符串经过排序后就是原字符串 S S S的一个子串,而且是 S S S的最大回文子串)
代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int cnt,r,m,p[22000001],len,maxn,id;
char ss[22000001];
string s;
int main()
{
cin>>s;
len=s.size()*2+1;
for(int i=1;i<=len;i++)
{
if(i&1)
ss[i]='#';
else
ss[i]=s[id++];
}
r=m=-1;
for(int i=1;i<=len;i++)
{
if(i>r)
p[i]=1;
else
p[i]=min(p[2*m-i],r-i+1);
while(i+p[i]<=len&&i-p[i]>=1)
{
if(ss[i+p[i]]==ss[i-p[i]])
p[i]++;
else
break;
}
if(i+p[i]>r)
{
r=i+p[i]-1;
m=i;
}
maxn=max(maxn,p[i]);
}
printf("%d",maxn-1);
return 0;
}