Manacher 学习笔记

Manacher 学习笔记

2021/02/23 看了 N 遍,终于看懂了。。。

推荐资料:OI-Wiki

同时这篇博客还借鉴了洛谷春令营网课的资料,不得不说获益匪浅。

1 基本概念

1.1回文串

对于一个字符串 s [ 1 … n ] s[1\dots n] s[1n],它是回文串当且仅当 ∀ i ∈ [ 1 , n ] , s [ i ] = s [ n − i + 1 ] \forall i\in[1,n],s[i]=s[n-i+1] i[1,n],s[i]=s[ni+1]

1.2 回文子串

t t t s s s 的子串且 t t t 是回文的,则称 t t t s s s 的回文子串。

1.3 回文中心

奇回文串的中心是它最中心的那个位置,而偶回文串的中心是它最中间两个字符的间隔位置。

对于一个字符串,定义它一个(奇长度/偶长度)回文子串的回文中心为字符串的(奇回文中心/偶回文中心)

2 求解回文串的几种方法

我们有多种方法求解回文串,这里简单列举几种:

  1. 利用后缀数组的 h e i g h t \rm height height 数组:将原串翻转再对接,对每个位置求正反串的最长公共前缀。一般时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  2. 字符串哈希,对每个位置 x x x 二分出最长的 r r r 使得 s [ x … x + r ] = s [ x − r … x ] s[x\dots x+r]=s[x-r\dots x] s[xx+r]=s[xrx]。时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  3. 接下来所说的 Manacher 算法。时间复杂度为 O ( n ) O(n) O(n)

3 Manacher 算法

3.1 一个巧妙的转化

为了避免奇回文中心和偶回文中心的讨论,我们在原串基础上增添一些字符。
d b a a b a b ⇒ & # d # b # a # a # b # a # b # d b a a b a b ⇒ & # d # b # a # a # b # a # b # 或 & # d # b # a # a # b # a # b # \begin{aligned} &\tt dbaabab \\ \Rightarrow&\tt \&\#d\#b\#a\#a\#b\#a\#b\# \\ &\tt d{\color{Red}baab}ab \\ \Rightarrow&\tt\&\#d{\color{Red}\#b\#a\#a\#b\#}a\#b\# \\ \mathtt{或}&\tt\&\#d\#{\color{Red}b\#a\#a\#b}\#a\#b\# \end{aligned} dbaabab&#d#b#a#a#b#a#b#dbaabab&#d#b#a#a#b#a#b#&#d#b#a#a#b#a#b#
于是我们把奇回文与偶回文统一了。但我们发现原串的回文串和新串的回文串不一定是一一对应的。这没有关系,我们后文再说。

这个 & \tt \& & 的作用是什么呢?是为了防止匹配上头忘记停下来了。

3.2 朴素算法

我们考虑怎么求一个位置可以延伸出去的最长回文子串长度。定义 r ( x ) r(x) r(x) 表示使得 s [ x + t ] ≠ s [ x − t ] s[x+t]\ne s[x-t] s[x+t]=s[xt] 的最小的 t t t。对每个点求解这个 r ( x ) r(x) r(x) 即可。时间复杂度为 O ( n 2 ) O(n^2) O(n2)

而 Manacher 其实是在朴素算法上做了个优化。

3.3 优化

情况1

比如一个字符串
x y z d a b a a a b a e f e a b a a a b a d x y z \tt xyzdabaaabaefe{\color{green}aba}{\color{Red}a}{\color{green}aba}dxyz xyzdabaaabaefeabaaabadxyz
我们要求它在 a \tt \color{red} a a 处的 r r r

假如我们已经知道了
x y z d a b a a a b a e f e a b a a a b a d x y z \tt xyz{\color{Green}dabaaabae}{\color{red}f}{\color{Green}eabaaabad}xyz xyzdabaaabaefeabaaabadxyz

x y z d a b a a a b a e f e a b a a a b a d x y z \tt xyzd{\color{Green}aba}{\color{red}a}{\color{Green}aba}efeabaaabadxyz xyzdabaaabaefeabaaabadxyz
由对称性可直接推得这两个 a \tt\color{red}a a r r r 是相同的。

情况2

类似于情况1:
x a b c b a e f e a b c b a e f y x a b c b a e f e a b c b a e f y {\tt x{\color{green}abcbae}{\color{red}f}{\color{green}eabcba}efy} \\ {\tt x{\color{green}ab}{\color{red}c}{\color{green}ba}efeabcbaefy} xabcbaefeabcbaefyxabcbaefeabcbaefy
可直接推得
x a b c b a e f e a b c b a e f y \tt xabcbaefe{\color{green}ab}{\color{red}c}{\color{green}ba}efy xabcbaefeabcbaefy
但还没完!还可以继续扩展
x a b c b a e f e a b c b a e f y \tt xabcbae{\color{green}feab}{\color{red}c}{\color{green}baef}y xabcbaefeabcbaefy

3.4 Manacher 算法流程

Manacher算法是基于上面的优化的。假如我之前所求得的回文子串中可以延伸到的最大右边界为 m = x + r ( x ) m=x+r(x) m=x+r(x),则如果现在循环到 i < m i<m i<m,则可以先设定
r ( i ) = min ⁡ ( m − i , r ( 2 × x − i ) ) r(i)=\min(m-i,r(2\times x-i)) r(i)=min(mi,r(2×xi))
然后再暴力拓展。

上面那个式子,第一项是为了不超出“已知范围”,第二个式子则是对称性的约束。直接看上面两个优化就知道了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
//char In[1 << 20], *ss = In, *tt = In;
//#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const int MAXN = 3e7 + 5;
char s[MAXN];
int n, r[MAXN];
int main() {
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for(int i = n; i >= 1; i--) s[i * 2 + 1] = s[i], s[i * 2 + 2] = '#';
	s[1] = '@', s[2] = '#'; n = 2 * n + 2;
	
	for(int c = 0, i = 1; i <= n; i++) {
		if(i < c + r[c]) r[i] = min(c + r[c] - i, r[2 * c - i]);
		while(i + r[i] <= n && s[i + r[i]] == s[i - r[i]]) r[i]++;
		if(i + r[i] > c + r[c]) c = i;
	}
	int ans = 0;
	for(int i = 1; i <= n; i++) ans = max(ans, r[i] - 1);
	printf("%d\n", ans);
	return 0;
}

3.5 Manacher算法的应用

求出每个点的 r r r 后,我们观察易知:

  1. 新串的最大回文子串长度为 max ⁡ ( 2 r ( x ) − 1 ) \max(2r(x)-1) max(2r(x)1),旧串的最大回文子串长度为 max ⁡ ( r ( x ) − 1 ) \max(r(x)-1) max(r(x)1)
  2. 回文子串总个数为 ∑ x = 1 n ⌊ r ( x ) 2 ⌋ \sum_{x=1}^n\left\lfloor\dfrac {r(x)}{2}\right\rfloor x=1n2r(x)

由此可见,我们直接通过 r r r 推出这些性质,不需要原串与新串一一对应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值