Manacher(马拉车)算法详解

 

       马拉车用于解决最长回文子串问题,重点是子串,而不是子序列,想了解最长回文子序列的可以看下这篇博客传送门。对于这种问题,当然最简单粗暴的方法就是暴力求解,但太暴力也不好,毕竟会TLE。所以对于求最长回文子串的问题有一种神奇的算法——马拉车算法,神奇就神奇在时间复杂度为O(n)。

       我先说一下大概思路,就是用一个Len[i]数组去存第i个位置到mx位置的长度,然后用id记录上一次操作的位置,mx标记上一次的最长子串的最右端,然后依次去递推。不太好理解(花了好几个小时才懂),现在看不懂没关系,我尽量讲的详细点。

       以hihoCoder的一道裸题为例,只要能AC就差不多懂了,题目链接:传送门

       先定义一个字符串s = "ababc",因为对于回文字符串来说有对称轴,比如说aba就是以b作为对称轴,那么当字符串长度为偶数的时候,对称轴就不是唯一的整数位了。所以我们要对原字符串

       做一个预处理,在每个字符左右都加上一个特殊字符,这里我用'#',那么处理后的字符串str就是#a#b#a#b#c#了,不管原字符串长度是奇数还是偶数,处理后的长度都变成奇数了。然后还需要在这个str的最前面加一个不同于之前的特殊字符的特殊字符,这里我用了'%'(因为在后面的while循环中在匹配字符的时候可能会越界)。这样预处理操作就完了,最后的字符串str就是%#a#b#a#b#c#了。

       首先需要明白的是,我定义的mx是上一次操作的最长回文子串的最右端,我解释一下,比如说ababa,第一次对i=2操作的时候,会计算出他的最回文子串为aba(长度为3),则在这次操作结束后mx的位置就是4,也就是aba的下一位。而我定义的id是上一次操作的位置,也就是i的位置,还以刚才的例子为例,在对i=2操作结束后,id的位置就是2。姑且先这么理解,知道他是什么就行,后面再去思考。

       Len数组里存的是第i个位置到mx位置的长度,比如说还是ababa,当i=2的时候,可以计算出aba是它此时的最长回文子串,那么mx的位置就是aba的下一位,那Len[2]存的就是mx - i了。最重要的就是Len数组,所以这点一定要弄明白,Len存的是,当前这个位置到它的最长回文子串的最右端的距离(也就是mx的位置)。在str字符串中,Len[i] = 2*Len[i] - 1(因为有特殊字符),在s字符串中Len[i] = mx - id + 1。

        知道这些后,开始进入正题,先看下核心代码。

Manacher(马拉车)算法详解_第1张图片

       首先把mx初始化0,然后开始从i=1开始遍历str,当i>=mx的时候,也就是i在mx前面的时候,就让Len[i] = 1,表示在i之前没有回文串出现,所以让Len从1开始,然后先不说i

               Manacher(马拉车)算法详解_第2张图片

       然后对于imx-i,如下图所示,Len[j]的长度的范围超出了my。

                 Manacher(马拉车)算法详解_第3张图片

       所以我们需要对这两种情况再讨论一下,当Len[j] < mx-i的时候,表示Len[i]的长度可能不会超过mx-i,所以我们就从i的Len[2*id - i]也就是Len[mx-i]的地方开始匹配。当Len[j] > mx - i的时候,说明i位置的子串长度超过了mx,但mx以外的地方还没有遍历到,所以我们就从mx-i也就是mx的位置开始对i匹配。

       然而最后的结果就是下图这样的

                           Manacher(马拉车)算法详解_第4张图片

       用一个sum去更新最大值就行了。我觉得挺不好理解的,建议耐下心想一想,模拟一遍,有不懂的都可以评论,有不对的地方也请指出。然后上一个前面说的那道题的代码,也是Manacher的模板。

 

AC代码:

#include 
#include 
#include 
#define Min(a,b) a>b?b:a
#define Max(a,b) a>b?a:b
using namespace std;
int Len[3000005];
char str[3000005],s[3000005];
int n,mx,id,len;

void init(){
    memset(str,0,sizeof(str));
    int k=0;
    str[k++] = '$';
    for(int i=0;i mx){
      mx = Len[i] + i;
      id = i;
      sum = Max(sum, Len[i]);
    }
  }
  return (sum - 1);
}

int main()
{
  scanf("%d",&n);
  while(n--){
    scanf("%s",s);
    len = strlen(s);
    init();
    int temp = Manacher();
    printf("%d\n",temp);
  }
  return 0;
}

 

 

 

         最后上一个手动模拟了一下午的草图。

Manacher(马拉车)算法详解_第5张图片

 

 

你可能感兴趣的:(ACM_动态规划,ACM_干货)