[最长回文字符串]manacher马拉车

manacher马拉车 https://www.luogu.com.cn/problem/P3805

闲言一下:花了一个中午终于把 manacher 给搞懂了。本文将以一个蒟蒻的身份来,来写写马拉车算法。首先请自行回顾暴力的 最长回文字符串 算法。首先我们将 通过枚举中心点,并扩展以该中心点为回文中心的回文串长度的算法称为 “中心扩展法”。manacher算法便是以“中心扩展法”为核心,利用枚举中获取的信息优化而来的。


具体地来说:

首先在解决奇偶回文字符串时,我们使用分类讨论的方法,解决了奇数和偶数的问题,但是这样未免过于麻烦,有什么可以将二者统一的方式呢?

这是处理前的字符串

具体地来说:

首先在解决奇偶回文字符串时,我们使用分类讨论的方法,解决了奇数和偶数的问题,但是这样未免过于麻烦,有什么可以将二者统一的方式呢?

这是处理前的字符串 a b b c 1 2 3 4   这是处理后的字符串 # a # b # b # c # 1 2 3 4 5 6 7 8 9  

我们通过加字符的方法,注意这里不一定要是 # ,也可以是其他字符,主要目标是为了将奇偶回文字符串统一了起来。这样在处理回文字符串时,会更加的方便。(不过也容易犯一些粗心的小问题,这些问题会在文章末尾提及)。

接下来回想一下 中心扩展法 求解最长回文序列的时候。遇到这种情况时

a b a d a b a  1 2 3 4 5 6 7

当我们枚举完 以下标为 4 的点为中心时,我们可以知道 1~7 为回文序列。加上前面以下标为3的点为中心时,我们可知道 1~3 为回文序列。根据回文序列的对称性,我们可以得出 5~7 也为回文序列。但在我们使用中心扩展法时,就需要多余地去枚举以下标为3的点为中心的回文半径的长度。这大大地浪费了时间。因此我们可以从这个角度入手,优化我们的算法。

从 中心扩展法 的基础上,再根据刚刚从特殊数据的角度,我们可以想到先定义几个变量maxr,mid ,用来表示当前已知的回文序列中最右的右端点,及该回文序列的中点。为什么要记录最右的右端点呢?因为右端点越右的回文序列就越容易包括后面还未遍历的中心,这样就能尽可能的达到,通过现有信息尽可能减少多余遍历的目的了。再定义一个数组 p[i] 表示以 i 为中点的回文序列的回文半径。

具体来讲一下优化的要点,

情况一

a b b c 
1 2 3 4

这是处理后的字符串
# a # b # b # c #
1 2 3 4 5 6 7 8 9

我们通过加字符的方法,注意这里不一定要是 # ,也可以是其他字符,主要目标是为了将奇偶回文字符串统一了起来。这样在处理回文字符串时,会更加的方便。(不过也容易犯一些粗心的小问题,这些问题会在文章末尾提及)。

接下来回想一下 中心扩展法 求解最长回文序列的时候。遇到这种情况时

a b a d a b a  1 2 3 4 5 6 7

当我们枚举完 以下标为 4 的点为中心时,我们可以知道 1~7 为回文序列。加上前面以下标为3的点为中心时,我们可知道 1~3 为回文序列。根据回文序列的对称性,我们可以得出 5~7 也为回文序列。但在我们使用中心扩展法时,就需要多余地去枚举以下标为3的点为中心的回文半径的长度。这大大地浪费了时间。因此我们可以从这个角度入手,优化我们的算法。

从 中心扩展法 的基础上,再根据刚刚从特殊数据的角度,我们可以想到先定义几个变量maxr,mid ,用来表示当前已知的回文序列中最右的右端点,及该回文序列的中点。为什么要记录最右的右端点呢?因为右端点越右的回文序列就越容易包括后面还未遍历的中心,这样就能尽可能的达到,通过现有信息尽可能减少多余遍历的目的了。再定义一个数组 p[i] 表示以 i 为中点的回文序列的回文半径。

具体来讲一下优化的要点,

情况一

[最长回文字符串]manacher马拉车

 

如果是上图的这种情况,则p[i]=p[j] , j=mid-(i-mid) --> j=2*mid-i 。(回文序列的对称性)

情况二

[最长回文字符串]manacher马拉车

 

如果是上图的这种情况,则不一定p[i]=p[j] ,因为超过ml ~ mr的部分并不满足回文序列的性质。所以只有在ml ~ mr的部分才能进入计算,因此p[i]=mr-i+1 。

重要的优化只有这些,其他的和 中心扩展法的做法大致相同。

#include<bits/stdc++.h> using namespace std; const int N=9*1e7; string str="!#",c;/*为什么第一位是“!”?                     为了避免程序第16行扩展时越界 */  int p[N],mid=0,mr=0,ans;  void work(){     cin>>c;      for(int i=0;c[i]>='a'&&c[i]<='z';i++){         str+=c[i];         str+='#';     }/*处理原序列*/      for(int i=1;i<str.size();i++){         if(i<=mr) p[i]=min(p[2*mid-i],mr-i+1);         /*i关于mid对称点; mid-(i-mid)——> 2*mid-i  */          while(str[i-p[i]]==str[i+p[i]]) p[i]++;         //这里其实就是扩展 回文序列的 回文半径          if(i+p[i]>mr){             mid=i;             mr=i+p[i]-1;          }         if(p[i]>ans) ans=p[i];      }     cout<<ans-1; } int main(){     work();     return 0; }


 

粗心的小问题(建议自行写完代码后查看)

1.因为要插入一堆字符,所以字符数组和其他相关数组的大小需要翻倍

发表评论

评论已关闭。

相关文章