Luogu_P3805 【模板】manacher算法

【模板】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 1n1.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 Si,如果 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} p2mi(这意味着以 i i i为中点的最大回文子串中的每个字符 S ′ j {S'}_j Sj,满足 ∀ 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 ri+1(这意味着以 i i i为中点的最大回文子串中的每个字符 S ′ j {S'}_j Sj,满足 ∀ 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(p2mi,ri+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 maxn1。(证明:在字符串 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值