【转载】Manacher算法

本文原创:http://www.cnblogs.com/BigBallon/p/3816890.html只为了记录学习,不为抄袭!http://www.felix021.com/blog/read.php?2040
对于Manacher算法,主要的作用是用来求一个字符串的最长回文子串。这个算法的时间复杂度书线性的,即O(n)下面我分两个部分来讲1)预处理这个算法的精妙之处在于巧妙地避免了考虑回文子串的长度是奇数还是偶数(如果你还不知道什么是回文数,回文串,请自行baidu)在Manacher算法中,需要提前预处理我们原本的字符串,这里把原串叫做s1, 把预处理之后的字符串叫做s2.那么,对于s1 = "ababba",      预处理后的s2 = "$#a#b#a#b#b#a#".这样一来,所有的回文串,在s2中,都是奇数了,你可以自己画一画或者看一看。这里注意一点,我们将s2[0] = ‘$‘,也就是一个字符串中没有出现的其他字符,为的是避免访问数组时越界。如果没有这个处理。在代码中的P[i] = min(P[2*id]-i),mx-i)处可能会出现这种情况:当进行到第二次循环,此时i=1,id = 0,2*id-i = -1,就出现了数组越界。。所以这个处理是必须的。

那么预处理代码大概是这样的:

void init()
{
    int i, j = 2;
    s2[0] = ‘$‘, s2[1] = ‘#‘;

    for(i=0;s1[i];i++)
    {
        s2[j++] = s1[i];
        s2[j++] = ‘#‘;
    }
    s2[j] = ‘\0‘;
}

2)Manacher算法

预处理完成之后,就可以用Manacher算法来求最长回文子串了。你会发现,所有和字符串相关的算法都很注重利用之前匹配过程中留下的有用信息,当然这个算法也不能例外。

首先,这里有一个辅助数组P[](针对s2而言的),记录的是在P[i]处,包括P[i]本身向左向右延伸的最大字符数。仔细想想你会明白P[i]-1表示的正是原串中对应位置的最回文子串的长度。  

//引用前面文章里面的内容
下面以字符串12212321为例
经过上一步,变成了 S[] = "$#1#2#2#1#2#3#2#1#";
然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系:
---------------------------------------------------------
S   #  1  #  2  #  2  #  1  #  2  #  3  #  2  #  1  #
P   1  2  1  2  5  2  1  4  1  2  1  6  1  2  1  2  1
---------------------------------------------------------
(p.s. 可以看出,P[i]-1正好是原字符串中回文串的总长度)

再来看怎么匹配以及利用有用信息,这里我们用一个变量 id 记录扩张长度位置信息, 用变量 mx 记录id位置处的扩张长度。先给出代码,结合代码来讲

void manacher(char* s)
{
    int i, id = 0, mx = 0;
    P[0] = 0;              //P[0]位置没用
    for(i=1;s[i];i++)      //对串进行线性扫描
    {
        if(mx > i)         //如果mx比当前i大,分为两种情况,详细致请看文章开头推荐的blog上的图示,非常给力的图
            P[i] = min(P[2*id-i],mx-i);
        else               //如果mx比i小,没有可以利用的信息,那么就只能从头开始匹配
            P[i] = 1;
        while(s[i + P[i]] == s[i - P[i]])    P[i]++;  //匹配
        if(mx < P[i] + i) //坚持是否有更新mx以及id
        {
            mx = P[i] + i;
            id = i;
        }
    }
}        

然后是几道题目:

1. HDU 3068 最长回文

题意:中文的,没什么好说的。模板题目,用来学习非常好

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 222222
#define CLR(a,b) memset(a,b,sizeof(a))
int p[MAXN];
char s1[MAXN],s2[MAXN];

void manacher(char* s)
{
int i, id = 0, mx = 0;
p[0] = 0;
for(int i=1;s[i]!=‘\0‘;i++)
{
if(mx > i)
p[i] = min(p[2*id-i],mx-i);
else
p[i] = 1;
while(s[i + p[i]] == s[i - p[i]]) p[i]++;
if(i + p[i] > mx)
{
mx = i + p[i];
id = i;
}
}
}

void init(char* s1,char*s2)
{
s2[0] = ‘$‘;
int i = 0;
int j = 1;
for(i=0;s1[i]!=‘\0‘;i++)
{
s2[j++] = ‘#‘;
s2[j++] = s1[i];
}
s2[j++] = ‘#‘;
s2[j] = ‘\0‘;
}

void getAC()
{
int res = 0;
for(int i=0;s2[i]!=‘\0‘;i++)
res = max(res,p[i]);

printf("%d\n",res-1);
}

int main()
{
while(~scanf("%s",s1))
{
init(s1,s2);
manacher(s2);
getAC();
}
return 0;
}

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 222222
#define CLR(a,b) memset(a,b,sizeof(a))
int p[MAXN];
char s1[MAXN],s2[MAXN];

void manacher(char* s)
{
    int i, id = 0, mx = 0;
    p[0] = 0;
    for(int i=1;s[i]!=‘\0‘;i++)
    {
        if(mx > i)
            p[i] = min(p[2*id-i],mx-i);
        else
            p[i] = 1;
        while(s[i + p[i]] == s[i - p[i]])    p[i]++;
        if(i + p[i] > mx)
        {
            mx = i + p[i];
            id = i;
        }
    }
}

void init(char* s1,char*s2)
{
    s2[0] = ‘$‘;
    int i = 0;
    int j = 1;
    for(i=0;s1[i]!=‘\0‘;i++)
    {
        s2[j++] = ‘#‘;
        s2[j++] = s1[i];
    }
    s2[j++] = ‘#‘;
    s2[j] = ‘\0‘;
}

void getAC()
{
    int res = 0;
    for(int i=0;s2[i]!=‘\0‘;i++)
        res = max(res,p[i]);

    printf("%d\n",res-1);
}

int main()
{
    while(~scanf("%s",s1))
    {
        init(s1,s2);
        manacher(s2);
        getAC();
    }
    return 0;
}

2. HDU 3294 Girls‘ research

题意:求最长回文子串,并求出起始位置。分析:本来就要预处理,这里多了一个预处理,一点也不难,反正都要处理。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define MAXN 200020
int P[MAXN<<1];
char s1[MAXN], s2[MAXN<<1];
int sta,end,res,id,ch;

void manacher(char* s)
{
int i, id = 0, mx = 0;
P[0] = 0;
for(i=1;s[i];i++)
{
if(mx > i)
P[i] = min(P[2*id-i],mx-i);
else
P[i] = 1;
while(s[i + P[i]] == s[i - P[i]]) P[i]++;
if(mx < P[i] + i)
{
mx = P[i] + i;
id = i;
}
}
}

void init()
{
int i = 0, j, key = ‘a‘ - ch;
for(i=0;s1[i];i++)
{
s1[i] += key;
if(s1[i] > ‘z‘)
s1[i] = s1[i] - ‘z‘ + ‘a‘ - 1;
if(s1[i] < ‘a‘)
s1[i] = ‘z‘ - (‘a‘ - s1[i]) + 1;
}
s2[0] = ‘$‘, s2[1] = ‘#‘;
for(i=0,j=2;s1[i];i++)
{
s2[j++] = s1[i];
s2[j++] = ‘#‘;
}
s2[j] = ‘\0‘;
}

void AC()
{
getchar();
init();
manacher(s2);
res = 0, id = 0;
for(int i=0;s2[i];i++)
{
if(res < P[i])
{
res = P[i];
id = i;
}
}
if(res < 3)
puts("No solution!");
else
{
// printf("id = %d\n",id);
// printf("res = %d\n",res);
sta = ((id - res+2)>>1) - 1;
end = sta + res - 2;
printf("%d %d\n",sta,end);
// printf("s1= %s, s2 = %s\n",s1,s2);
for(int i=sta;i<=end;i++)
printf("%c",s1[i]);
puts("");
}

}

int main()
{
while(~scanf("%c %s",&ch,s1))
{
AC();
}

return 0;
}

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define MAXN 200020
int P[MAXN<<1];
char s1[MAXN], s2[MAXN<<1];
int sta,end,res,id,ch;

void manacher(char* s)
{
    int i, id = 0, mx = 0;
    P[0] = 0;
    for(i=1;s[i];i++)
    {
        if(mx > i)
            P[i] = min(P[2*id-i],mx-i);
        else
            P[i] = 1;
        while(s[i + P[i]] == s[i - P[i]])    P[i]++;
        if(mx < P[i] + i)
        {
            mx = P[i] + i;
            id = i;
        }
    }
}

void init()
{
    int i = 0, j, key = ‘a‘ - ch;
    for(i=0;s1[i];i++)
    {
        s1[i] += key;
        if(s1[i] > ‘z‘)
            s1[i] = s1[i] - ‘z‘ + ‘a‘ - 1;
        if(s1[i] < ‘a‘)
            s1[i] = ‘z‘ - (‘a‘ - s1[i]) + 1;
    }
    s2[0] = ‘$‘, s2[1] = ‘#‘;
    for(i=0,j=2;s1[i];i++)
    {
        s2[j++] = s1[i];
        s2[j++] = ‘#‘;
    }
    s2[j] = ‘\0‘;
}

void AC()
{
    getchar();
    init();
    manacher(s2);
    res = 0, id = 0;
    for(int i=0;s2[i];i++)
    {
        if(res < P[i])
        {
            res = P[i];
            id = i;
        }
    }
    if(res < 3)
        puts("No solution!");
    else
    {
    //    printf("id = %d\n",id);
    //    printf("res = %d\n",res);
        sta = ((id - res+2)>>1) - 1;
        end = sta + res - 2;
        printf("%d %d\n",sta,end);
    //    printf("s1= %s, s2 = %s\n",s1,s2);
        for(int i=sta;i<=end;i++)
            printf("%c",s1[i]);
        puts("");
    }

}

int main()
{
    while(~scanf("%c %s",&ch,s1))
    {
        AC();
    }

    return 0;
}


3. POJ 3974 Palindrome

题意:同HDU3068

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))
#define MAXN 1111111
int P[MAXN<<1], cas = 1;
char s1[MAXN], s2[MAXN<<1];

void manacher(char* s)
{
int mx = 0, i, id = 0;
P[0] = 0;
for(i=1;s[i];i++)
{
if(mx > i)
P[i] = min(P[2*id-i],mx-i);
else
P[i] = 1;
while(s[i + P[i]] == s[i - P[i]]) P[i]++;
if(mx < P[i] + i)
{
mx = P[i] + i;
id = i;
}
}
}

void init()
{
int i, j = 2;
s2[0] = ‘$‘, s2[1] = ‘#‘;

for(i=0;s1[i];i++)
{
s2[j++] = s1[i];
s2[j++] = ‘#‘;
}
s2[j] = ‘\0‘;
}

void AC()
{
printf("Case %d: ",cas++);
int res = 0, i = 0;
for(i=0;s2[i];i++)
{
if(res < P[i])
res = P[i];
}
printf("%d\n",res-1);
}

int main()
{
while(~scanf("%s",s1))
{
if(strcmp("END",s1) == 0)
break;
init();
manacher(s2);
AC();
}
return 0;
}

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))
#define MAXN 1111111
int P[MAXN<<1], cas = 1;
char s1[MAXN], s2[MAXN<<1];

void manacher(char* s)
{
    int mx = 0, i, id = 0;
    P[0] = 0;
    for(i=1;s[i];i++)
    {
        if(mx > i)
            P[i] = min(P[2*id-i],mx-i);
        else
            P[i] = 1;
        while(s[i + P[i]] == s[i - P[i]])    P[i]++;
        if(mx < P[i] + i)
        {
            mx = P[i] + i;
            id = i;
        }
    }
} 

void init()
{
    int i, j = 2;
    s2[0] = ‘$‘, s2[1] = ‘#‘;

    for(i=0;s1[i];i++)
    {
        s2[j++] = s1[i];
        s2[j++] = ‘#‘;
    }
    s2[j] = ‘\0‘;
}

void AC()
{
    printf("Case %d: ",cas++);
    int res = 0, i = 0;
    for(i=0;s2[i];i++)
    {
        if(res < P[i])
            res = P[i];
    }
    printf("%d\n",res-1);
}

int main()
{
    while(~scanf("%s",s1))
    {
        if(strcmp("END",s1) == 0)
            break;
        init();
        manacher(s2);
        AC();
    }
    return 0;
}


4. HDU 4513 吉哥系列故事——完美队形II

题意:求最长回文子串,而外要求:从回文串最中间向两边满足非递增。分析:一开始不知道怎么解决,后来想想在manacher函数中加一个判断,然后就A了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 100111
int P[MAXN<<1], s1[MAXN], s2[MAXN<<1];
int t,n;
void manacher(int* s)
{
int id = 0, i, mx = 0;
P[0] = 0;
for(i=1;i<2*n+1;i++)
{
if(mx > i)
P[i] = min(P[2*id-i],mx-i);
else
P[i] = 1;
while(s[i + P[i]] == s[i - P[i]])
{
if(s[i + P[i]] != -222)
{
if(s[i + P[i]] <= s[i + P[i] - 2])
P[i]++;
else
break;
}
P[i]++;
}
if(mx < i + P[i])
{
id = i;
mx = P[i] + i;
}
}
}

void init()
{
s2[0] = -111, s2[1] = -222;
int i = 0,j = 2;
for(i=0;i<n;i++)
{
s2[j++] = s1[i];
s2[j++] = -222;
}
}

void AC()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&s1[i]);
init();
manacher(s2);
int ret = -1;
for(int i=0;i<2*n+1;i++)
{
if(P[i] > ret)
ret = P[i];
}
printf("%d\n",ret - 1);
}

int main()
{
scanf("%d",&t);
while(t--)
{
AC();
}
}

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 100111
int P[MAXN<<1], s1[MAXN], s2[MAXN<<1];
int t,n;
void manacher(int* s)
{
    int id = 0, i, mx = 0;
    P[0] = 0;
    for(i=1;i<2*n+1;i++)
    {
        if(mx > i)
            P[i] = min(P[2*id-i],mx-i);
        else
            P[i] = 1;
        while(s[i + P[i]] == s[i - P[i]])
        {
            if(s[i + P[i]] != -222)
            {
                if(s[i + P[i]] <= s[i + P[i] - 2])
                    P[i]++;
                else
                    break;
            }
            P[i]++;
        }
        if(mx < i + P[i])
        {
            id = i;
            mx = P[i] + i;
        }
    }
}

void init()
{
    s2[0] = -111, s2[1] = -222;
    int i = 0,j = 2;
    for(i=0;i<n;i++)
    {
        s2[j++] = s1[i];
        s2[j++] = -222;
    }
}

void AC()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&s1[i]);
    init();
    manacher(s2);
    int ret = -1;
    for(int i=0;i<2*n+1;i++)
    {
        if(P[i] > ret)
            ret = P[i];
    }
    printf("%d\n",ret - 1);
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        AC();
    }
}

时间: 2024-10-09 11:26:15

【转载】Manacher算法的相关文章

浅谈Manacher算法与扩展KMP之间的联系

首先,在谈到Manacher算法之前,我们先来看一个小问题:给定一个字符串S,求该字符串的最长回文子串的长度.对于该问题的求解,网上解法颇多,时间复杂度也不尽相同,这里列述几种常见的解法. 解法一 通过枚举S的子串,然后判断该子串是否为回文,由于S的子串个数大约为,加上每次判断需要的时间,所以总的时间复杂度为,空间复杂度为. bool check(string &S, int left, int right) { while (left < right && S[left]

[转] Manacher算法详解

转载自: http://blog.csdn.net/dyx404514/article/details/42061017 Manacher算法 算法总结第三弹 manacher算法,前面讲了两个字符串相算法——kmp和拓展kmp,这次来还是来总结一个字符串算法,manacher算法,我习惯叫他 “马拉车”算法. 相对于前面介绍的两个算法,Manacher算法的应用范围要狭窄得多,但是它的思想和拓展kmp算法有很多共通支出,所以在这里介绍一下.Manacher算法是查找一个字符串的最长回文子串的线

Manacher算法详解

[转] Manacher算法详解 转载自: http://blog.csdn.net/dyx404514/article/details/42061017 Manacher算法 算法总结第三弹 manacher算法,前面讲了两个字符串相算法——kmp和拓展kmp,这次来还是来总结一个字符串算法,manacher算法,我习惯叫他 “马拉车”算法. 相对于前面介绍的两个算法,Manacher算法的应用范围要狭窄得多,但是它的思想和拓展kmp算法有很多共通支出,所以在这里介绍一下.Manacher算法

hdu5371 Hotaru&#39;s problem(manacher 算法+枚举)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5371 题目大意:给一串数字,在子串中找到"1-2-1"的形式,其中1和2 是回文串,找出最长的那一串. 思路:利用manacher算法得出最长序列.观察子串形式,1和2是回文串,其实2和后面那个1也是回文串. 在之前我们已经通过manacher算法得到了每个数字所能延伸的长度,所以我们只要枚举第二段到第三段的距离即可. 当遇到第i个数字时,那么第二段和第三段的距离就是i+p[i],令j=i

HDU Hotaru&#39;s problem(Manacher算法+贪心)

manacher算法详见 http://blog.csdn.net/u014664226/article/details/47428293 题意:给一个序列,让求其最大子序列,这个子序列由三段组成,第一段和第二段对称,第一段和第三段一样. 思路:首先利用Manacher算法求出以任意两个相邻元素为中心的回文串长度,用a[i]表示i-1,i为中心的回文串长度的一半, 那么问题就转化成了求最大的x,使得a[i]>=x,a[i+x]>=x,这一步可以贪心来做. 将a[i]从大到小排序(间接排序保留

hdu3068 最长回文(manacher 算法)

题意: 给定字符串,求字符串中的最长回文序列 解题思路: manacher 算法 时间复杂度:O(N) 代码: #include <cstdio> #include <cstring> #include <algorithm> #define MAXN 110010 using namespace std; char b[MAXN],a[MAXN<<1]; int p[MAXN<<1]; int n; int main(){ while(scan

hdu 3068 最长回文串 o(n) Manacher 算法

最长回文 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 10596    Accepted Submission(s): 3759 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度. 回文就是正反读都是一样的字符串,如aba, abba等 Input 输入有多

Manacher算法(转)

O(n)回文子串算法 注:转载的这篇文章,我发现下面那个源代码有点bug...在下一篇博客中改正了.. 这里,我介绍一下O(n)回文串处理的一种方法.Manacher算法.原文地址:http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/ 其实原文说得是比较清楚的,只是英文的,我这里写一份中文的吧. 首先:大家都知道什

查找字符串中的最长回文字符串---Manacher算法

转载:https://www.felix021.com/blog/read.php?2040 首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号.比如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#. 为了进一步减少编码的复杂度,可以在字符串的开始和结尾加入另一个特殊字符这样就不用特殊处理越界问题,比如%#a#b#a#@;(如果是C++,字符串末尾有一个\0,故结尾处不需要添加额为的特殊字符@) 然后用一个数组