回文串查找 - manacher算法

Manacher’s   Algorithm \texttt{Manacher's Algorithm} Manacher’s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher \texttt{Manacher} Manacher 的人在 1975 \texttt{1975} 1975 年发明的,广大 OIer \texttt{OIer} OIer都叫他马拉车算法。

当然还有些人管她叫没马拉车比如说wxd

查找回文串最简单最暴力的方法,是用 O ( n 3 ) \tt O({n}^{3}) O(n3) 的方法进行暴力枚举

一重循环用于枚举中心点,一重循环用于枚举半径,一重用于判断是不是回文串。

后来,人们改进了以上的算法,可以保持在 O ( n 2 ) \tt O(n^2) O(n2) 的复杂度之内。

一重用于枚举中心点,一重用于从小到大枚举半径,随便在枚举过程中判断回文串。

最后, Manacher \texttt{Manacher} Manacher 算法被发明了,用 O ( n ) \tt O(n) O(n) 的复杂度算出回文子串。

前置知识

无(字符串?)

算法用途

用于查找一个字符串的最长回文子串

算法复杂度

时间

O ( l e n ) \tt O(len) O(len)

空间

O ( l e n ) \tt O(len) O(len)

算法实现

回文串有个比较烦的情况,他的中心点可能在两个字符之间,也可能在一个字符上。

Manachar \texttt{Manachar} Manachar 对此的优化是在两个字符之间增加另一种字符,比如说:

1 2 3 4 5 6 7 8 \tt\kern 0.525em 1\kern 0.525em 2\kern 0.525em 3\kern 0.525em 4\kern 0.525em 5\kern 0.525em 6\kern 0.525em 7\kern 0.525em 8 12345678

# 1 # 2 # 3 # 4 # 5 # 6 # 7 # 8 # \tt\#1\#2\#3\#4\#5\#6\#7\#8\# #1#2#3#4#5#6#7#8#

这样,就可以把任意长度的字符串的长度转换为奇数了。

我们定义一个数组, P \tt P P P [ i ] \tt{P[i]} P[i] 代表以第 i \tt i i 个字符为中心点的子回文串的半径。

s t r : # a # b # a # c # a # b # a # b # b # \tt str:\#a\#b\#a\#c\#a\#b\#a\#b\#b\# str:#a#b#a#c#a#b#a#b#b#

P : 1214121812141412321 \tt \kern 0.525em\kern 0.525emP:1214121812141412321 P:1214121812141412321

问题来了,我们只知道这个回文串的半径长度,还需要知道他起始位置才能确定这个回文串的位置。

我们虽然不知道他的起始位置,我们知道他的中心点位置,我们可以用这个来推出起始位置。

一般情况下,回文串的中点位置减去他的半径就是他的起始点位置,我们的半径的定义是他左边的字符的长度 + 1。这样一减去,就会到他起始点的前一个字符。

我们其实只需要加一就好了,但是 O I e r \tt OIer OIer 们都喜欢在这个字符串前面在加一个不同的字符防止访问越界,顺便就弥补了这个问题。

s t r : @ # a # b # a # c # a # b # a # b # b # \tt str:@\#a\#b\#a\#c\#a\#b\#a\#b\#b\# str:@#a#b#a#c#a#b#a#b#b#

我们需要注意,这里只是在添加了 # \tt \# # 之后的字符串,我们如果要找回之前的,我们只需要 ÷   2 \tt\div~2 ÷ 2 就行了。

好,现在只剩如何推出 P \tt P P 数组了。

我们设 m r \tt mr mr 为当前算出的回文串能到达的最右边位置 + 1(不在回文串里), i d \tt id id 为能到达最右边的位置的回文串的中心点。只需要理解这个核心句子,就几乎可以理解 M a n a c h e r \tt Manacher Manacher

P [ i ] = m r > i   ?   m i n ( P [ 2 × i d − i ] , m r − i ) : 1 \tt P[i] = mr > i~?~min(P[2 \times id - i], mr - i) : 1 P[i]=mr>i ? min(P[2×idi],mri):1

我们解释一下该句子:

m r > i \tt mr > i mr>i 的时候, P [ i ] = P [ 2 × i d − i ] \tt P[i] = P[2 \times id - i] P[i]=P[2×idi],但是这个回文串延伸的最远位置不能超过 m r \tt mr mr

m r < = i \tt mr <= i mr<=i 时,回文串的半径初始化为 1 \tt 1 1。、

我们只需要理解 P [ i ] = P [ 2 × i d − i ] \tt P[i] = P[2 \times id - i] P[i]=P[2×idi],就能理解这个句子了。

我们可以把这个句子转换成另一个: P [ i ] = P [ i d − ( i − i d ) ] \tt P[i] = P[id - (i - id)] P[i]=P[id(iid)]

i d − ( i − i d ) \tt id - (i - id) id(iid) 的意思是在 i d \tt id id 往前移动 i \tt i i i d \tt id id 的距离。还记得吗, i d \tt id id 代表延伸最远的回文串的中心点。

因为回文串是两边对称的,这个回文串中间如果包含回文串,而且包含的回文串不是在中点的话,那么肯定有两个。比如说:

s t r : @ # d # a # b # a # a # c # a # a # b # a # d # b # c # \tt str:@\color{red}\#d\color{blue}{\#a\#b\#a\#}\color{red}a\#c\#a\color{blue}\#a\#b\#a\#\color{red}d\#\color{black}b\#c\# str:@#d#a#b#a#a#c#a#a#b#a#d#b#c#

在红色的回文串中间,有两个对称的蓝色的回文串。

s t r : @ # d # a # b # a # a # c # a # a # b # a # d # b # c # \tt str:@\color{red}\#d\color{blue}{\#a\#b\#a\#}\color{red}a\#c\#a\color{blue}\#a\#b\#a\#\color{red}d\#\color{black}b\#c\# str:@#d#a#b#a#a#c#a#a#b#a#d#b#c#
i d i \tt \kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.3em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525em\tiny{id}\normalsize\kern 0.375em\kern 0.525em\kern 0.525em\kern 0.525em\kern 0.525emi idi

这时候, P [ 2 × i d − i ] \tt P[2\times id-i] P[2×idi] 也就是另一个蓝色区域的半径。

记住,我们在执行完这个操作后。还需要用第二种方法(暴力优化1次后那种)来扩展这个回文串,不然算出来的回文串可能不是最长的。

代码

#include<iostream>
using namespace std;

int p[2000010];
int ans = 0;

int motherless_pull_car(string str)
{
	string s = "@#";
	for(int i = 0; i < str.size(); i++)
	{
		s += str[i];
		s += '#';
	}
	int mr = 0, id = 0;
	for(int i = 1; i <= s.size(); i++)
	{
		p[i] = mr > i ? min(p[(id << 1) - i], mr - i) : 1;
		while(s[i - p[i]] == s[i + p[i]])
		{
			p[i]++;
		}
		if(i + p[i] > mr)
		{
			mr = i + p[i];
			id = i;
		}
		ans = max(ans, p[i] - 1);
	}
	return 0;
}

int main()
{
	string str;
	cin >> str;
	motherless_pull_car(str);
	cout << ans << endl;
	return 0;
}

例题

P3805【模板】manacher   算法 \color{3498DB}{\texttt{P3805【模板】manacher~算法}} P3805【模板】manacher 算法

P4555   [国家集训队]最长双回文串 \color{3498DB}{\texttt{P4555 [国家集训队]最长双回文串}} P4555 [国家集训队]最长双回文串

P5446   [THUPC2018]绿绿和串串 \color{9D3DCF}{\texttt{P5446 [THUPC2018]绿绿和串串}} P5446 [THUPC2018]绿绿和串串

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值