Manacher算法

前(che)言(dan)

最近做了很多字符串的题目,其中有一些就要用到Manacher算法,故花了一上午去学习学习。

网上题解很多,不保证大家看一篇就能懂,建议多看几篇。

那么,下面这些,就当做是我的学习笔记与对算法的理解吧!

预备知识

子串:字符串中连续的一段字符序列,如abc的子串有a,b,c,ab,bc,abc.

回文串:假设字符串str长度为len,,假设字符串位置从1开始,对于任意i,都有str[len-i+1]=str[i],如abcba就是一个回文串。

回文子串:满足回文串条件的子串。

有何用?

求解一个字符串中最长的回文子串,洛谷有模板题:https://www.luogu.org/problemnew/show/P3805

尝试求解

枚举所有子串再检查还不行吗?时间复杂度为O(n^3).

能不能更快一点?

好吧,我们容易发现,回文串是左右对称的。

我们先假设回文串长度为奇数,那么必定存在一个位置i使得i左右子串对称,我们枚举这个i,找出关于i中心对称的最长回文子串。

但是假设最长回文串为偶数呢?

我们在开头,结尾,以及相邻两个字符间插入字符'0'(其它的也行,只要不出现在原串中),这样它的长度就变为了奇数,然后按照上述做法,最后把长度对2整除即可。

比如:aaa\rightarrow 0a0a0a0;

i=1:1,'0';(以第1'0'为中心的最长回文串长度为1,最长回文串为'0'

i=2:3,'0a0';

i=3:5,'0a0a0';

i=4:7,'0a0a0a0'.

显然以i=4为回文中心的回文子串最长,那么原串最长回文子串为\left \lfloor\frac{7}{2} \right \rfloor=3

时间复杂度为O(n^{2}).

能不能再快一点?

好了,正文来了。(为了不至于您看文章的过程中犯困,我把重点加粗标记了起来)

我们发现,虽然上述算法已经比较优了,但还是有很多子串被重复枚举。

引入nxt[]数组,其中nxt[i]表示以i为回文中心向右延伸的最长长度。

比如说对于aaanxt[2]=2,因以第二个'a'为回文中心的最长回文子串为'aaa',故从第二个'a'向右可延伸最大长度为2,即原串第23位。

引入r,now,其中r表示当前所有回文子串延伸到的最远右端点,now为其对应的回文串的回文中心。

这就要求,回文中心还是要存在的,偶数长度的回文串还是不能有的。

那么就像上一种方法那样,无脑插入字符,可以证明,无论是奇数还是偶数长度的串,都可以通过上述方法变为奇数长度的串。

然后就是比较重要的一条性质:nxt[i]=min(nxt[now*2-i],r-i+1).

首先now*2-i有什么意义呢?列举几个数据发现,它是i关于now中心对称的位置;那r-i+1呢?其实就是ri之间的字符串长度.

j=now*2-il=r-i+1,则:

[i,r]之间的字符与[j-l+1,j]之间的字符是相同的。

l,j代入,则[i,r][now*2-r,j],由定义知r=now+nxt[now]-1,故:[now*2-r,j]=[now-nxt[now]+1,j].

最终结论:[i,r][now-nxt[now]+1,j]的字符是相同的。

len=r-i+1,故:

r-i+1\leq nxt[j],字符相同的这段区间被以j为回文中心的字符串所包含,那么由上述推出的对称相等,以i为中心的回文串长度为r-i+1,即nxt[i]=r-i+1

r-i+1 >nxt [j],多出的部分虽然相等但不回文,仍取较小值,即nxt[i]=nxt[j]=nxt[now*2-i]

这样就避免了重复子串的枚举,时间效率大大提高。

之后的过程就简单了,通过一行while(str[nxt[i]-1]==str[nxt[i]+1])nxt[i]++;,就可轻松把子串向左右两边延展;同时,不要忘了更新nowr

最终的结果就为max\left\{nxt[i]\right\}-1,这是因为我们在预处理时在相邻字符间插入了新的字符,故nxt[]的值,本应表示回文串长度+1之后的一半,成为了回文串的长度+1+1是因为回文中心被多算了一遍)。

因为不会枚举到重复的字串,最终均摊下来的时间复杂度为O(n).

参考代码:

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
	int n,cnt;
	char c[12000000];
	char str[23000000];
	int nxt[23000000];
int main()
{
	char s=getchar();
	while (s>='a'&&s<='z')
	{
		c[++n]=s;
		s=getchar();
	}
	for (int i=1;i<=n;i++)
	{
		str[++cnt]='#';
		str[++cnt]=c[i];
	}
	str[++cnt]='#';
	int now=0;
	int r=0;
	int ans=0;
	for (int i=1;i<=cnt;i++)
	{
		if (i<=r) nxt[i]=min(nxt[now*2-i],r-i+1);
		while (str[i+nxt[i]]==str[i-nxt[i]]&&i>nxt[i]) nxt[i]++;
		if (nxt[i]+i-1>r)
		{
			r=nxt[i]+i-1;
			now=i;
		}
		if (nxt[i]-1>ans)
			ans=nxt[i]-1;
	}
	printf("%d",ans);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值