Manacher算法(模版)

算法介绍:

Manacher详解

个人理解:

Manacher算法可以在线性时间复杂度(O(2n))内求出一个字符串的最长回文子串。同时可以处理出以每个元素为中心的最长回文子串。

由于它把原字符串s每个元素之间加了'#"(包括首尾),所以总共加了n+1个'#',所以转换后的字符串 t 总共有2*n+1个元素,元素个数一定为奇数,所以就不需要再分奇偶考虑,这个是巧妙点之一。

Manacher关键在于求解length数组,length[i]其含义是i到以 i 为中心的最长回文子串右端点的距离。比如说以 i 为中心的最长回文子串是[l,r],length[i] = r-i 。length数组是对t字符串求解的。但它有一个性质:length[i]-1就是s字符串以i为中心的最长回文子串的长度

现在有个问题,如果t[i]=='#',那length[i]代表着什么?这就是刚才上面讲到的奇偶的问题。如果一个回文子串为奇数,比如aabaa,那么对应它的length[i]=6,且t[i]='b'。但如果一个回文子串为偶数,比如abba,那么对应它的length[i]=5,且t[i]='#'(相当于以'#'为中心)。

接下去最重要的就是length[ ]的求解:

首先从左往右依次计算length[i],当计算length[i]时,length[j](0<=j<i)已经计算完成。设P为之前计算中最长回文子串的右端点的最大值,并且设该回文子串的中心为po。

第一种情况:i<=P

这里还要再分2种情况:

第一种:i+length[i]<P(如下图),那么i关于po的对称点j其实是一样的(因为对称嘛),所以length[i]=length[2*po-i] 。

第二种:i+length[i]>=P(如下图),那么i到P的这部分肯定是和j到2po-P的这部分一样的,但超出部分是可能不一样的,所以要遍历超出部分,直到发生失配,从而更新P和对应的po以及length[i]。

第二种情况:i>P

如果i比P还要大,说明对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新P的位置和对应的po以及length[i]。

因为算法只有遇到还没有匹配的位置时才进行匹配,已经匹配过的位置不再进行匹配,所以对于T字符串中的每一个位置,只进行一次匹配,所以Manacher算法的总体时间复杂度为O(n),其中n为t字符串的长度,由于t的长度事实上是s的两倍,所以时间复杂度依然是线性的。

Code:

纯净版本:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAX = 110000+10;
const ll mod = 1e9+7;

char s[MAX]; 
char t[MAX<<1]; 
int length[MAX<<1]; 

 int trans(int len)
 {
    int cnt=1;
    t[0]='@'; 
    for(int i=0;i<len;i++){
        t[cnt++]='#';
        t[cnt++]=s[i];
    }
    t[cnt]='#';
    t[cnt+1]='$'; 
    t[cnt+2]='\0';
    return cnt; 
 }

 int Manacher(int len)
 {
    int ans=0,po=0,Max=0; 
    for(int i=1;i<=len;i++){
        if(i<Max){
            length[i]=min(length[2*po-i],Max-i); 
        }
        else{
            length[i]=1; 
        }
        while(t[i+length[i]]==t[i-length[i]]) length[i]++;
        if(i+length[i]>Max){
            Max=i+length[i];
            po=i;
        }
        ans=max(ans,length[i]);
    }
    return ans-1;
 }

int main()
{
    while(scanf("%s",s)!=EOF)
    {
        int len = strlen(s);
        int tlen = trans(len);
        int ans = Manacher(tlen);
        printf("%d\n",ans);
    }
    return 0;
}

注释版本:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAX = 110000+10;
const ll mod = 1e9+7;

char s[MAX]; //原字符串
char t[MAX<<1]; //变换后的字符串(可以奇偶同时考虑)
int length[MAX<<1]; //length[i]-1就是该回文子串(以 i 为中心)在原字符串S中的长度

//转换原始串
 int trans(int len)
 {
    int cnt=1;
    t[0]='@'; //字符串开头增加一个特殊字符,防止越界
    for(int i=0;i<len;i++){
        t[cnt++]='#';
        t[cnt++]=s[i];
    }
    t[cnt]='#';
    t[cnt+1]='$'; //字符串结尾加一个字符,防止越界
    t[cnt+2]='\0';
    return cnt; //返回转换后字符串t的长度
 }

//Manacher算法计算过程
 int Manacher(int len) //这个len是t[ ]的长度
 {
    int ans=0,po=0,Max=0; //Max即为当前计算回文串最右边字符的最大值
    for(int i=1;i<=len;i++){
        if(i<Max){
            length[i]=min(length[2*po-i],Max-i); //在length[j]和Max-i中取较小值
        }
        else{
            length[i]=1; //如果i>=Max,要从头开始匹配
        }
        while(t[i+length[i]]==t[i-length[i]]) length[i]++;
        //若新计算的回文串右端点位置大于Max,要更新po和Max的值
        if(i+length[i]>Max){
            Max=i+length[i];
            po=i;
        }
        ans=max(ans,length[i]);
    }
    return ans-1;//返回length[i]中的最大值-1即为原串的最长回文子串长度 
 }

int main()
{
    while(scanf("%s",s)!=EOF)
    {
        int len = strlen(s);
        int tlen = trans(len);
        int ans = Manacher(tlen);
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/luyehao1/article/details/84920118