Manacher算法

 

参考资料:

  [1]:左神进阶班 [ 提取码:3knt ]

 

自学笔记:

  1.何为Manacher算法(摘自百度百科)?

    Manachar算法主要是处理字符串中关于回文串的问题的;

    它可以在O(n)的时间处理出以字符串中每一个字符为中心的回文串半径;

  2.相关概念介绍

    (1):何为回文半径?

      例如:

      0  1  2  3  4

      a  b  c  b  a

      以0处的a为中心的最长回文子串为 a ,其回文半径长度为 1(a);

      以1处的b为中心的最长回文子串为 b ,其回文半径长度为 1(b);

      以2处的c为中心的最长回文子串为 abcba ,其回文半径长度为 3(cba);

      以3处的b为中心的最长回文子串为 b ,其回文半径长度为 1(b);

      以1处的a为中心的最长回文子串为 a ,其回文半径长度为 1(a);

    (2):何为回文半径数组?

      定义radius[]为回文数组,那么 radius[ i ] 指的是以第 i 个字符为中心的回文半径长度。

    (3):何为最右回文边界?

      还是以上一个例子为例:

      以0处的a为中心的最右回文边界为 0;

      以1处的b为中心的最右回文边界为 1(超过了0-'a'字符所达到的最右端,更新最右回问边界);

      以2处的c为中心的最右回文边界为 4(超过了1-'b'字符所达到的最右端,更新最右回问边界);

      以3处的b为中心的最右回文边界为 4(未超过2-'c'字符所达到的最右端,不更新最右回文边界);

      以4处的a为中心的最右回文边界为 4(未超过2-'c'字符所达到的最右端,不更新最右回文边界);

  3.Manacher算法介绍

    介绍完了基本概念后,开始回归正题;

    定义字符数组 s[] 为所要求的字符串;

    ①首先,需要将 s[] 预处理,在开始位置和结尾位置,以及字符间加上字符 ‘#’

    例如,假设 s[] = "abcba";

    那么,预处理后,字符串变为 s[]="#a#b#c#b#a#";

    ②假设radius[0,......i-1]已求出,达到的最右回文边界为R,第一个达到最右回文边界的下标为 k

    那么,如何高效的求解dp[ i ]呢?

    1)如果 i ≥ R ,暴力扩

      比对 (i+1,i-1) , (i+2,i-2) , .........  直到求出不相等位置或越界,求解出radius[ i ];

      ( (i+1,i-1) : 判断 s[ i+1 ] 是否等于 s[ i-1 ] )

    2)如果 i < R

      求出 i 关于 k 的对称点 j = k-(i-k)

      [1]:如果 i+radius[ j ]-1 < R , 那么 radius[ i ]=radius[ j ];

      [2]:如果 i+radius[ j ]-1 > R , 那么 radius[ i ]=R-i+1;

      [3]:如果 i+radius[ j ]-1 = R , 定义 tot = radius[ j ] , 判断 (i+tot,i-tot) , (i+tot+1,i-tot-1) , ....... 直到求出不相等位置或越界,求解出radius[ i ];

    证明详见参考资料;

模板:

 1 struct Manacher
 2 {
 3     char s[maxn<<1];
 4     int r[maxn<<1];
 5 
 6     void Init(char *ss,int len)
 7     {
 8         int index=0;
 9         s[index++]='#';
10         for(int i=0;i < len;++i)
11         {
12             s[index++]=ss[i];
13             s[index++]='#';
14         }
15         ///加上保险,防止多组输入遗留上次的信息,因为我习惯于用strlen(s)求长度
16         s[index]='\0';
17     }
18     void manacher()
19     {
20         int R=-1;///最右回文边界
21         int K;///最右会问边界对应的中心下标
22         int len=strlen(s);
23         for(int i=0;i < len;++i)
24         {
25             int cnt=0;
26             if(i >= R)///情况(1)暴力扩
27             {
28                 for(;i+cnt < len && i-cnt >= 0 && s[i+cnt] == s[i-cnt];cnt++);
29                 r[i]=cnt;
30             }
31             else///情况(2)的三种小情况,[1],[2]可合并为 != R
32             {
33                 int j=2*K-i;///i关于k的对称点
34                 if(i+r[j]-1 != R)///不压边界
35                     r[i]=min(r[j],R-i+1);///取最小值
36                 else///压线才往两边暴力扩
37                 {
38                     cnt=R-i;
39                     for(;i+cnt < len && i-cnt >= 0 && s[i+cnt] == s[i-cnt];cnt++);
40                     r[i]=cnt;
41                 }
42             }
43             if(i+cnt-1 > R)///判断是否更新R,K
44             {
45                 R=i+cnt-1;
46                 K=i;
47             }
48         }
49     }
50 }_mana;
View Code

之前年轻不懂事,原来 manacher 代码还可以更精简QWQ;

精简代码:

 1 /**
 2     Manacher算法在O(n)的时间复杂度下求解串s的回文半径
 3 */
 4 struct Manacher
 5 {
 6     char s[maxn<<1];///注意数组大小,根据题意而定
 7     int r[maxn<<1];///最右回文半径数组
 8 
 9     ///传入待求解字符串ss,注意ss下标要从0开始,len是ss的长度
10     void Init(char *ss,int len)
11     {
12         int index=0;
13         s[index++]='#';
14         for(int i=0;i < len;++i)
15         {
16             s[index++]=ss[i];
17             s[index++]='#';
18         }
19         s[index]='\0';///最好加上,防止多组输入时遗留上一次的信息
20     }
21     void manacher()
22     {
23         int len=strlen(s);
24         int R=-1;///最右回文边界,正好压住边界
25         int C;///最右回文边界对应的中点下标
26         for(int i=0;i < len;++i)
27         {
28             r[i]=R > i ? min(R-i+1,r[2*C-i]):1;
29             for(;i-r[i] >= 0 && i+r[i] < len && s[i-r[i]] == s[i+r[i]];r[i]++);
30             if(i+r[i]-1 > R)///i处的右回文区间[i,i+r[i]-1]
31             {
32                 R=i+r[i]-1;///R正好压住,所以r[i]赋初值时是R-i+1而不是R-i
33                 C=i;
34             }
35         }
36     }
37 }_mana;
View Code

 

posted @ 2019-03-11 21:27  HHHyacinth  阅读(165)  评论(0编辑  收藏  举报