浅谈 Manacher

从某种方面来说,Manacher 算法是朴素 O(n2) 暴力算法的优化。。。

那就得先了解一下 Manacher 的朴素算法------

朴素算法

枚举中心点并不断向外展开(例如:[i,i][i+1,i+1][i+2,i+2]

缺点:

  1. 时间复杂度:O(n2)———慢
  2. 不是特别好处理长度为偶数的回文串———菜

Manacher

想要优化,首先得解决几个问题:

如何处理长度为偶数的回文串?

可以这样:在每个字符串间及开头结尾加上一个特殊字符(例子:aaaa ~#a#a#a#a#

考虑例子中为什么开头第一个地方有个 ~

这个后面代码中再说(防止数组越界)。。。

如何使时间复杂度降到线性?

记录一个数组p[i]表示以 i 为回文中心的回文半径。

现在就是处理来到 i 这个点,如何转移p[i]

我们在这里维护一个当前回文串最右端点 r,和其对称中心 mid

ir

因为 i+p[i]1r

所以 p[i]ri+1

我们通过以 mid 为对称轴得到一个与当前的 i 对称的点 j(这个对称点点的p[]已经处理出来)来转移p[i],同时还能向外扩展:while(s[i-p[i]]==s[i+p[i]])++p[i];

j 就用初一的中点公式:i+j2=midj=2midi

所以 p[i]=min(p[2midi],ri+1)

i>r

p[i]=1,就是不能向外扩展(只有自己本身的长度)。

板子代码:

#include <bits/stdc++.h>
using namespace std;
const int N=3e7+5;
int n,p[N];
char s[N],st[N];
int cnt=1; 
void init()
{
	s[0]='~';
	s[cnt++]='#';
	for(int i=0;i<n;i++)
	{
		s[cnt++]=st[i];
		s[cnt++]='#';
	}
}
int main()
{
	cin>>st;
	n=strlen(st);
	init();
	int ans=-1;
	for(int i=1,r=0,mid=0;i<=cnt;i++)
	{
		if(i<=r)p[i]=min(p[2*mid-i],r-i+1);
		while(s[i-p[i]]==s[i+p[i]])++p[i];//解释一下:s的第一位那个~,就是在while循环中防止越界
		if(i+p[i]>r)r=p[i]+i-1,mid=i;
		if(ans<p[i])ans=p[i];
	}
	cout<<ans-1;
	return 0;
}

小牛试刀

P6216 回文匹配

这题不是特别好像,考虑要想发挥出字符串真正的线性魅力,一般需要一些线性算法才能更加完美。

这题加的是二次前缀和

首先用 KMP算法 求出 s2s1 中出现的每一个位置,并准备第一次前缀和 s[i](在每个出现位置的左端点 +1)。其次,再用 Manacher 求一下每个回文半径。最后,发现其实这个答案形如 s[r]s[l1]+s[r1]s[l]+s[r2]s[l+1]+

整理得 s[r]+s[r1]+s[r2]+s[l]s[l+1]s[l+2]

发现可以将前缀和数组再次进行前缀和来得到答案。

posted @   tyccyt  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示