自己来总结下这个算法吧
觉得这篇文章写得非常好,而且图画得很好
http://www.felix021.com/blog/read.php?2040
对于Manacher算法,主要的作用是用来求一个字符串的最长回文子串。
这个算法的时间复杂度书线性的,即O(n)
下面我分两个部分来讲
1)预处理
这个算法的精妙之处在于巧妙地避免了考虑回文子串的长度是奇数还是偶数(如果你还不知道什么是回文数,回文串,请自行baidu)
在Manacher算法中,需要提前预处理我们原本的字符串,这里把原串叫做s1, 把预处理之后的字符串叫做s2.
那么,对于s1 = "ababba",
预处理后的s2 = "$#a#b#a#b#b#a#".
这样一来,所有的回文串,在s2中,都是奇数了,你可以自己画一画或者看一看。这里注意一点,我们将s2[0] = '$',也就是一个字符串中没有出现的其他字符,为的是避免访问数组时越界。如果没有这个处理。在代码中的P[i] = min(P[2*id]-i),mx-i)处可能会出现这种情况:
当进行到第二次循环,此时i=1,id = 0,2*id-i = -1,就出现了数组越界。。所以这个处理是必须的。
那么预处理代码大概是这样的:
void init() { int i, j = 2; s2[0] = '$', s2[1] = '#'; for(i=0;s1[i];i++) { s2[j++] = s1[i]; s2[j++] = '#'; } s2[j] = '\0'; }
2)Manacher算法
预处理完成之后,就可以用Manacher算法来求最长回文子串了。
你会发现,所有和字符串相关的算法都很注重利用之前匹配过程中留下的有用信息,当然这个算法也不能例外。
首先,这里有一个辅助数组P[](针对s2而言的),记录的是在P[i]处,包括P[i]本身向左向右延伸的最大字符数。仔细想想你会明白P[i]-1表示的正是原串中对应位置的最回文子串的长度。
//引用前面文章里面的内容 下面以字符串12212321为例 经过上一步,变成了 S[] = "$#1#2#2#1#2#3#2#1#"; 然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系: --------------------------------------------------------- S # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 # P 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1 --------------------------------------------------------- (p.s. 可以看出,P[i]-1正好是原字符串中回文串的总长度)
再来看怎么匹配以及利用有用信息,这里我们用一个变量 id 记录扩张长度位置信息, 用变量 mx 记录id位置处的扩张长度。
先给出代码,结合代码来讲
void manacher(char* s) { int i, id = 0, mx = 0; P[0] = 0; //P[0]位置没用 for(i=1;s[i];i++) //对串进行线性扫描 { if(mx > i) //如果mx比当前i大,分为两种情况,详细致请看文章开头推荐的blog上的图示,非常给力的图 P[i] = min(P[2*id-i],mx-i); else //如果mx比i小,没有可以利用的信息,那么就只能从头开始匹配 P[i] = 1; while(s[i + P[i]] == s[i - P[i]]) P[i]++; //匹配 if(mx < P[i] + i) //坚持是否有更新mx以及id { mx = P[i] + i; id = i; } } }
然后是几道题目:
1. HDU 3068 最长回文
题意:中文的,没什么好说的。模板题目,用来学习非常好
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MAXN 222222 #define CLR(a,b) memset(a,b,sizeof(a)) int p[MAXN]; char s1[MAXN],s2[MAXN]; void manacher(char* s) { int i, id = 0, mx = 0; p[0] = 0; for(int i=1;s[i]!='\0';i++) { if(mx > i) p[i] = min(p[2*id-i],mx-i); else p[i] = 1; while(s[i + p[i]] == s[i - p[i]]) p[i]++; if(i + p[i] > mx) { mx = i + p[i]; id = i; } } } void init(char* s1,char*s2) { s2[0] = '$'; int i = 0; int j = 1; for(i=0;s1[i]!='\0';i++) { s2[j++] = '#'; s2[j++] = s1[i]; } s2[j++] = '#'; s2[j] = '\0'; } void getAC() { int res = 0; for(int i=0;s2[i]!='\0';i++) res = max(res,p[i]); printf("%d\n",res-1); } int main() { while(~scanf("%s",s1)) { init(s1,s2); manacher(s2); getAC(); } return 0; }
2. HDU 3294 Girls' research
题意:求最长回文子串,并求出起始位置。
分析:本来就要预处理,这里多了一个预处理,一点也不难,反正都要处理。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAXN 200020 int P[MAXN<<1]; char s1[MAXN], s2[MAXN<<1]; int sta,end,res,id,ch; void manacher(char* s) { int i, id = 0, mx = 0; P[0] = 0; for(i=1;s[i];i++) { if(mx > i) P[i] = min(P[2*id-i],mx-i); else P[i] = 1; while(s[i + P[i]] == s[i - P[i]]) P[i]++; if(mx < P[i] + i) { mx = P[i] + i; id = i; } } } void init() { int i = 0, j, key = 'a' - ch; for(i=0;s1[i];i++) { s1[i] += key; if(s1[i] > 'z') s1[i] = s1[i] - 'z' + 'a' - 1; if(s1[i] < 'a') s1[i] = 'z' - ('a' - s1[i]) + 1; } s2[0] = '$', s2[1] = '#'; for(i=0,j=2;s1[i];i++) { s2[j++] = s1[i]; s2[j++] = '#'; } s2[j] = '\0'; } void AC() { getchar(); init(); manacher(s2); res = 0, id = 0; for(int i=0;s2[i];i++) { if(res < P[i]) { res = P[i]; id = i; } } if(res < 3) puts("No solution!"); else { // printf("id = %d\n",id); // printf("res = %d\n",res); sta = ((id - res+2)>>1) - 1; end = sta + res - 2; printf("%d %d\n",sta,end); // printf("s1= %s, s2 = %s\n",s1,s2); for(int i=sta;i<=end;i++) printf("%c",s1[i]); puts(""); } } int main() { while(~scanf("%c %s",&ch,s1)) { AC(); } return 0; }
3. POJ 3974 Palindrome
题意:同HDU3068
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a,b) memset(a,b,sizeof(a)) #define MAXN 1111111 int P[MAXN<<1], cas = 1; char s1[MAXN], s2[MAXN<<1]; void manacher(char* s) { int mx = 0, i, id = 0; P[0] = 0; for(i=1;s[i];i++) { if(mx > i) P[i] = min(P[2*id-i],mx-i); else P[i] = 1; while(s[i + P[i]] == s[i - P[i]]) P[i]++; if(mx < P[i] + i) { mx = P[i] + i; id = i; } } } void init() { int i, j = 2; s2[0] = '$', s2[1] = '#'; for(i=0;s1[i];i++) { s2[j++] = s1[i]; s2[j++] = '#'; } s2[j] = '\0'; } void AC() { printf("Case %d: ",cas++); int res = 0, i = 0; for(i=0;s2[i];i++) { if(res < P[i]) res = P[i]; } printf("%d\n",res-1); } int main() { while(~scanf("%s",s1)) { if(strcmp("END",s1) == 0) break; init(); manacher(s2); AC(); } return 0; }
4. HDU 4513 吉哥系列故事——完美队形II
题意:求最长回文子串,而外要求:从回文串最中间向两边满足非递增。
分析:一开始不知道怎么解决,后来想想在manacher函数中加一个判断,然后就A了
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MAXN 100111 int P[MAXN<<1], s1[MAXN], s2[MAXN<<1]; int t,n; void manacher(int* s) { int id = 0, i, mx = 0; P[0] = 0; for(i=1;i<2*n+1;i++) { if(mx > i) P[i] = min(P[2*id-i],mx-i); else P[i] = 1; while(s[i + P[i]] == s[i - P[i]]) { if(s[i + P[i]] != -222) { if(s[i + P[i]] <= s[i + P[i] - 2]) P[i]++; else break; } P[i]++; } if(mx < i + P[i]) { id = i; mx = P[i] + i; } } } void init() { s2[0] = -111, s2[1] = -222; int i = 0,j = 2; for(i=0;i<n;i++) { s2[j++] = s1[i]; s2[j++] = -222; } } void AC() { scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&s1[i]); init(); manacher(s2); int ret = -1; for(int i=0;i<2*n+1;i++) { if(P[i] > ret) ret = P[i]; } printf("%d\n",ret - 1); } int main() { scanf("%d",&t); while(t--) { AC(); } }