• 马拉车算法模板


    大佬博客

    马拉车用于解决最长回文子串问题,重点是子串,而不是子序列,想了解最长回文子序列的可以看下这篇博客传送门。对于这种问题,当然最简单粗暴的方法就是暴力求解,但太暴力也不好,毕竟会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。

    知道这些后,开始进入正题,先看下核心代码。
    在这里插入图片描述

    首先把mx初始化0,然后开始从i=1开始遍历str,当i>=mx的时候,也就是i在mx前面的时候,就让Len[i] = 1,表示在i之前没有回文串出现,所以让Len从1开始,然后先不说i<mx的情况,然后往下走到while循环,这个循环就是用来判断这个位置的最长回文子串的,就是以这个点为中心,然后依次比较左边一个和右边一个、左边第二个和右边第二个…是否相等,相等的话就让Len[i]++,就相当于这个位置到这个位置的子串的最右端的长度++。然后再比较这个点+这个点的回文长度是否超过了mx的位置,超过的话就更新一下mx的值,因为mx是此次操作的最长回文子串的最右端。
    在这里插入图片描述
    然后对于i<mx的情况就稍微有点不太好理解了,参考着上图我讲一下这种情况。这是一个以id(上一次操作的位置)为中点长度为mx-my的回文串,j是以id为中点的i的对称点,因为j在之前遍历的时候就已经操作过了,所以Len[j]是已知的又因为回文串的性质可以知道Len[j]<=Len[i]的,my是mx的对称点。对于i<mx的情况还要再分情况,就是Len[j]和mx-i比较了,上图的情况是Len[j]是小于mx-i的,表示j位置的最长回文子串的长度是在id位置的最长回文子串的范围内的,还有一种情况就是Len[j]>mx-i,如下图所示,Len[j]的长度的范围超出了my。
    在这里插入图片描述
    所以我们需要对这两种情况再讨论一下,当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匹配。
    然而最后的结果就是下图这样的
    在这里插入图片描述
    用一个sum去更新最大值就行了。我觉得挺不好理解的,建议耐下心想一想,模拟一遍,有不懂的都可以评论,有不对的地方也请指出。然后上一个前面说的那道题的代码,也是Manacher的模板。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define Min(a,b) a>b?b:a
    #define Max(a,b) a>b?a:b
    using namespace std;
    const int N = 2e6 + 10 ;
    int Len[N] , a[N] ;
    char str[N] ;
    int n,mx,id,len;
    string s ;
    void init(int l , int r){
        int k=0;
        str[k++] = '$';
        for(int i=l;i<=r;i++){
            str[k++]='#';
            str[k++]=s[i];
        }
        str[k++]='#';
        len=k;
        str[k] = '' ;
    }
    int ans = 0 , flag ;
    int Manacher(){
      Len[0] = 0;
      int sum = 0;
      mx = 0;
      id = 0 ;
      // cout << str << endl ;
      for(int i=1;i<len;i++){
        if(i < mx) Len[i] = Min(mx - i, Len[2 * id - i]);
        else Len[i] = 1;
        while(str[i - Len[i]]== str[i + Len[i]]) Len[i]++;
        if(Len[i] + i > mx){        // 更新最长的回文串
          mx = Len[i] + i;          // mx是回文串右边一个位置
          id = i;             //id是回文串的中心
          sum = Max(sum, Len[i]);         // sum 是回文串的长度 + 1
    
        }
        // cout << i << " " << Len[i] << endl ;
        // if(Len[i] == i)  表示前缀是回文的
        //  {
        //    if(ans < Len[i])
        //     ans = Len[i] - 1 , flag = 1 ;
        //  }
        // if(Len[i] + i == len)  表示后缀是回文的
        //  if(ans < Len[i])
        //   ans = Len[i] - 1 , flag = 2 ;
      }
      return sum - 1 ;
    }
    
    int main()
    {
      scanf("%d",&n);
      while(n--){
        cin >> s ;
        len = s.size();
        int l = 0 , r = len - 1 ;
        init(l , r);
        ans = 0 , flag = 0 ;
        // cout << Manacher() << endl ;
      }
      return 0;
    }
    
    
  • 相关阅读:
    本地数据存储
    网络统计公式
    网络统计图形
    上海证券综合指数统计分析及挖掘(二)
    上海证券综合指数统计分析及挖掘(一)
    概论
    寿险精算学目录
    JavaScript脚本代码练习
    JavaScript脚本语言基础(一)
    层叠样式表(CSS)
  • 原文地址:https://www.cnblogs.com/spnooyseed/p/12870850.html
Copyright © 2020-2023  润新知