C/C++教程

Manacher算法

本文主要是介绍Manacher算法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

问题

给出一个字符串,求出最长的回文串。

思路

朴素的想法:枚举字符串上的每一位,以其为回文串的中心进行扩展,统计答案。
这种方法是O(N^2)的,不优秀。

接下来考虑线性做法:
先将字符串中间插入特殊符号,以处理偶数长度的回文串。
对于每个回文串,我们可以给它记两个信息,即中心和半径r。再记录一个值mx代表当前扩展到的最右边界,以及这个回文串的中心p。
枚举每一个字符,分类讨论(以下j为i关于p的对称点):
(1)\(mx\leq i\),r[i]\(\geq\) 1(此时j无法给i提供信息)

(2)\(mx-i>r[j]\),r[i]=r[j](由于回文串的对称性可得)

(3)\(mx-i\leq r[j]\),r[i]\(\geq\)mx-i(由于[mx',mx]外的对称性无法满足,j给i提供了一个下界)

在初始的r[i]上再进行扩展即可,并更新mx和p。
由于扩展成功会导致mx右移,而mx右移不超过n次,故左右扩展总复杂度是O(N),枚举i也是O(N),故总时间复杂度为O(N)。

例题:

双倍回文

题意

记字符串x的倒置为\(x^R\),如果字符串s可以写成\(ww^rww^r\),则称它是一个双倍回文。
现在给出一个字符串,求出最长的双倍回文子串。

思路

在manacher中,考虑以当前枚举到的i为中心求双倍回文串。
当r[i]可以从r[j]推过来(情况2、3),我们就不需要判断这一部分的回文串,因为它与j处的回文串相同。
由此可知,一个字符串中本质不同的回文子串数量级是O(N)的
于是在每次mx扩展成功时再枚举。因为mx最多右移n次,所以这个复杂度也是O(N)的。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

const int N = 5e5 + 1;
int n, ans;
int hw[N << 1];
char a[N], s[N << 1];

int main() {
	scanf("%d", &n);
    scanf("%s", a);
    s[0] = '@';
	s[1] = '#';
    for (int i = 0; i < n; i++) {
        s[i * 2 + 2] = a[i];
        s[i * 2 + 3] = '#';
    }
    n = n * 2 + 2;
    s[n] = 0;
    int mx = 0, mid;
    for (int i = 1; i < n; i++) {
        if (i < mx)
            hw[i] = std::min(hw[mid * 2 - i], mx - i);
        else
			hw[i] = 1;
		while (s[i + hw[i]] == s[i - hw[i]])
			hw[i]++;
        if (hw[i] + i > mx) {
        	if (i & 1)
        		for (int j = std::max(i + 4, mx); j <= i + hw[i]; j++) {
        			if (!(j - i & 3) && hw[i - (j - i) / 2] >= (j - i) / 2)
        				ans = std::max(ans, j - i);
				}
            mx = hw[i] + i;
            mid = i;
        }
    }
    printf("%d", ans);
}

这篇关于Manacher算法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!