manacher 最长回文子串

喜闻乐见的zdc废话时间

之前听ckw讲过一次,但当时限于知识接受能力,并没有学懂事实上都没有听进去 然后决定就在子鼠年的最后一天学一下,结果发现这东西并不难,大概总结一下吧

manacher简介

用于求解最长回文子串问题,由于谐音,又名马拉车算法。

时间复杂度是线性的/se/se/se

具体实现

先考虑一个小问题

回文串有两类,一种是长度为奇数的,对称中心是字符;一种是长度为偶数的,对称中心是字符间隙。因为懒我们并不喜欢分类讨论,所以我们做一点处理把这两种情况归为一种

具体的,我们在字符之间和字符串头尾添加一个串中未出现过的字符(我喜欢用"#"),然后我们就会发现回文串都变成了奇数种(这里可以画图理解一下)

考虑如何暴力的求最长回文子串

暴力枚举所有端点然后暴力向两边扩展,时间复杂度是O(n^2)

那么来分析一下这个暴力算法的缺点

——有很多重叠的回文串被重复统计(大回文串套小回文串)

那么我们就从这个缺点入手

回文串有一个小性质:对于一个大回文串,设其对称中心为mid,其中的一个回文子区间关于mid的对称区间必为回文串。

那么我们设r当前回文子串最长向右扩展位置,mid为其对称中心,对于一个点i,设p[i]为以i为对称中心的最长回文子串长度。

考虑怎么求p[i]

结合之前所述性质,i的真实回文半径必定大于等于其关于mid的对称点的真实回文半径,而且这个性质是在大回文串才会成立

所以形式化的

p[i]=min(p[mid*2-i],r-i+1);

ps:这里mid * 2 - i 就是i关于mid的对称点,具体求法大概是这样,设对称点为 j ,显然,mid为 i,j 中点,根据中点公式:mid = ( i + j ) / 2。移项可得,j = (mid<<1) - i。

最后因为是大于等于号所以我们再去尝试扩展,然后更新r和mid

最终答案就为 max{ p [ i ] }-1 (画图理解一下吧,注意这里的串我们已经加字符了哦)

先贴一下模板代码(缺省源自带快读请忽视)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define _rep(i,x,y) for(int i=x;i>=y;i--)
#define int long long
#define N 22000010
const int inf=1e18;
inline int read()
{
	int num=0,fu=1; char c=getchar();
	while(c!='-'&&(c>'9'||c<'0')) c=getchar();
	if(c=='-') fu=-1,c=getchar();
	while(c>='0'&&c<='9') num=(num<<3)+(num<<1)+(c^48),c=getchar();
	return fu*num;
}

int tmp,n,ans,p[N];
char s[N],a[N];

signed main()
{
	scanf("%s",a+1);
	tmp=strlen(a+1);
	s[0]='#'; s[1]='#';
	rep(i,1,tmp) s[i*2]=a[i],s[i*2+1]='#';
	n=tmp*2+1;
	int mid=0,mxr=0;
	rep(i,1,n)
	{
		if(i<=mxr) p[i]=min(mxr-i+1,p[(mid<<1)-i]);
		while(s[i-p[i]]==s[i+p[i]]) p[i]++;
		if(i+p[i]-1>mxr) mxr=i+p[i]-1,mid=i;
	}
	rep(i,1,n) ans=max(ans,p[i]-1);
	printf("%lld\n",ans);
	return 0;
}

时间复杂度分析

在上述过程中,r(代码中为mxr)只会向右移动,最终也只会移动到串尾(结合双指针的时间复杂度分析),所以复杂度是线性的。

posted @ 2021-03-07 10:58  Ing1024  阅读(46)  评论(0)    收藏  举报