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;
之前年轻不懂事,原来 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;