Manacher算法模板

传送门

Manacher算法是解决回文串长度计算的利器。

优秀的算法大多起源于暴力的思想……我们一步一步来看。

首先思考最暴力的情况怎么匹配?枚举所有区间然后判断是不是回文串,时间复杂度O(n^3).

之后我们考虑优化一下。因为回文串的左右是相同的,所以我们不妨可以枚举回文串的中点,之后向两边依次拓展,直到不能再拓展为止,这样枚举是O(n),每次扫一遍也是O(n)的,时间复杂度变成了O(n^2)的。

然后想想上面的还能怎么优化。

1.首先一个问题是我们要考虑字符串长度的奇偶性,如果是奇数的话其中心能选到一个字符,不过如果是偶数的话那么就会选到两个字符中间的位置。我们解决的方法是把每两个字符中间加上一个原串中没有出现过的字符,比如‘#’。注意一定是要加相同的,因为这样是不会影响字符串匹配的。之后我们就把所有的字符串改成了奇数长度的。

2.在第二种算法中有多种情况被重复计算。

这里就要说到算法的主体了。我们建立一个辅助数组p[i]表示第i个位置能拓展出的最大的回文子串的长度的半径。这样的话一个位置的最长回文串长度就是p[i]-1。(因为里面有好多被填充过的字符)再设一个变量mx表示当前回文串能拓展到的最右边的位置,mid表示当前回文串的中心。

然后利用回文串左右两端相同的性质,如果当前枚举的点i在mx的左边而且在mid的右边,设i关于mid的对称点是j,那么我们就可以知道p[i] >= p[j](因为左右子串是相同的)其中j的计算方法是mid<<1 - i.之后我们继续向两边进行拓展即可(就像算法2一样)。

如果当前枚举到的点在mx的右边,这时候因为mx右边所有的点还没有被匹配,我们无法保证两边的字符串是相同的,这时就只能先设置p[i]=1,之后暴力匹配。

这样的话我们就做完了……是不是很简单?

这个算法的时间复杂度是O(n)的,所以是解决在长字符串中最长回文串长度的有力武器。至于它为什么是O(n)的,因为其实你的匹配是与mx有关的,mx是保证单调递增的。具体说一点,就是我们首先更新这个点能匹配到的最长回文串半径长度,之后我们再向后匹配的时候,如果你是通过翻转转移过来,它下一位匹配应该会失败(否则你一开始的长度会更长),真正有意义的匹配都是从mx开始的,只要从mx开始你才有可能获得一个正确的匹配。因为mx保证单调递增,所以算法的时间复杂度是O(n)的。

算法的代码非常简洁。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('
')
using namespace std;
typedef long long ll;
const int M = 100005;
const int N = 10000005;
 
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
    if(ch == '-') op = -1;
    ch = getchar();
    }
    while(ch >='0' && ch <= '9')
    {
    ans *= 10;
    ans += ch - '0';
    ch = getchar();
    }
    return ans * op;
}

char s[N<<2],c[N<<1];
int len,p[N<<2];

int change()//将字符串改造
{
    int l = strlen(c),j = 2;
    s[0] = '!',s[1] = '#';//注意最开始和最末尾是要换成另一种字符的
    rep(i,0,l-1) s[j++] = c[i],s[j++] = '#';
    s[j] = '&';
    return j;
}

int manacher()
{
    int len = change(),mx = 1,mid = 1,ans = 1;
    rep(i,1,len-1)
    {
    if(i < mx) p[i] = min(mx - i,p[(mid<<1)-i]);//更新这个点的最大回文串匹配长度
    else p[i] = 1;
    while(s[i-p[i]] == s[i+p[i]]) p[i]++;//如果两边能匹配上,那么长度++
    if(mx < i + p[i]) mid = i,mx = i + p[i];//如果mx已经在当前的匹配中心和匹配半径之和的左面,那么我们更新mx和mid
    ans = max(ans,p[i]-1);//更新答案,每一位的答案就是p[i]-1
    }
    return ans;
}

int main()
{
    scanf("%s",c);
    printf("%d
",manacher());
    return 0;
}
原文地址:https://www.cnblogs.com/captain1/p/9686150.html