KMP && Manacher && 扩展KMP整理
Posted kongbursi-2292702937
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP && Manacher && 扩展KMP整理相关的知识,希望对你有一定的参考价值。
KMP算法:
kmp示例代码:
void cal_next(char *str, int *next, int len) { next[0] = -1;//next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀 int k = -1;//k初始化为-1 for (int q = 1; q <= len-1; q++) { while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。 { k = next[k];//往前回溯 } if (str[k + 1] == str[q])//如果相同,k++ { k = k + 1; } next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q] } } int KMP(char *str, int slen, char *ptr, int plen) { int *next = new int[plen]; cal_next(ptr, next, plen);//计算next数组 int k = -1; for (int i = 0; i < slen; i++) { while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配) k = next[k];//往前回溯 if (ptr[k + 1] == str[i]) k = k + 1; if (k == plen-1)//说明k移动到ptr的最末端 { //cout << "在位置" << i-plen+1<< endl; //k = -1;//重新初始化,寻找下一个 //i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠),感谢评论中同学指出错误。 return i-plen+1;//返回相应的位置 } } return -1; } char *str = "bacbababadababacambabacaddababacasdsd"; char *ptr = "ababaca"; int a = KMP(str, 36, ptr, 7); return 0;
kmp算法是用来找模式串是否在主串中出现,并返回第一次出现的位置。(模式串一般都比主串长度短,求的是模式串在主串中是否出现)
它有一个数组next[len](len是ptr字符串的长度),next[i]这里面放的是模式串的前i个字符的最长公共前后缀。(前缀不包括第i个字符)
时间复杂度:O(n+m)
算法过程:
你求的是模式串在主串中出现的位置,next[i]数组是模式串前i个字符的最长公共前后缀。
S1=abadfgag
S2=abac
next[0]=-1
next[1]=-1
next[2]=1
next[3]=-1
第一次匹配从S1第0位开始,得到S1[0,2]==S2[0,2]
那么下一次匹配还要从S1第1位开始吗?
不需要。kmp优化的就是这里,因为next[2]=1
那么就有S2[0]=S2[2],又因为S1[0,2]==S2[0,2]
所以S1[0]==S1[2],那么我们这次就可以直接从S1第3位开始匹配
例题:传送门
题意:
t组输入,给你一个模式串ptr,让你找出来它在str中出现的次数
题解:
利用KMP算法,只需要在kmp原来的基础上稍微改一下就可以用来求次数
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=1000005; 7 const int INF=0x3f3f3f3f; 8 char str[maxn],ptr[maxn]; 9 int ans; 10 void get_next(int len,int *next) 11 { 12 next[0]=-1; 13 int k=-1; 14 for(int i=1;i<=len-1;++i) 15 { 16 while(k>-1 && ptr[k+1]!=ptr[i]) 17 k=next[k]; 18 if(ptr[k+1]==ptr[i]) 19 { 20 k+=1; 21 } 22 next[i]=k; 23 } 24 } 25 void kmp(int slen,int plen) 26 { 27 int next[plen]; //这个next数组长度只能定义plen个长度,大的话会出错 28 get_next(plen,next); 29 int k=-1; 30 for(int i=0;i<slen;++i) 31 { 32 while(k>-1 && ptr[k+1]!=str[i]) 33 { 34 k=next[k]; 35 } 36 if(ptr[k+1]==str[i]) 37 { 38 k=k+1; 39 } 40 if(k==plen-1) 41 { 42 ans++; 43 k=next[k]; 44 //i=i-plen+1+1; 45 } 46 } 47 } 48 int main() 49 { 50 int t; 51 scanf("%d",&t); 52 while(t--) 53 { 54 ans=0; 55 scanf("%s%s",ptr,str); 56 int slen=strlen(str); 57 int plen=strlen(ptr); 58 kmp(slen,plen); 59 printf("%d ",ans); 60 } 61 return 0; 62 }
有些题目利用next数组的特性来做,比如G - Seek the Name, Seek the Fame
这道题目需要找出来模式串中前后缀的所有相同长度,而不只是求最长
比如“alala”,它的最长相同前后缀是“ala”,但是还有“a”。等到把所有前后缀相同长度输出之后,再输出一下模式串长度就可以了。
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=400005; 7 const int INF=0x3f3f3f3f; 8 char ptr[maxn]; 9 int w[maxn]; 10 void get_next(int len,int *next) 11 { 12 next[0]=-1; 13 int k=-1; 14 for(int i=1;i<=len-1;++i) 15 { 16 while(k>-1 && ptr[k+1]!=ptr[i]) 17 k=next[k]; 18 if(ptr[k+1]==ptr[i]) 19 { 20 k+=1; 21 } 22 next[i]=k; 23 } 24 } 25 int main() 26 { 27 int n; 28 while(~scanf("%s",ptr)) 29 { 30 int len=strlen(ptr); 31 int next[len]; 32 get_next(len,next); 33 int g=0,k=next[len-1]; 34 //w[g++]=next[len-1]+1; 35 while(k>=0) 36 { 37 w[g++]=k+1; 38 k=next[k]; 39 40 } 41 sort(w,w+g); 42 for(int i=0;i<g;++i) 43 if(w[i]!=w[i-1] && i!=0) 44 printf("%d ",w[i]); 45 else if(i==0) printf("%d ",w[i]); 46 printf("%d ",len); 47 } 48 return 0; 49 }
扩展kmp:
扩展KMP求的是对于原串S1的每一个后缀子串与模式串S2的最长公共前缀。它有一个next[]数组和一个extend[]数组。
next[i]表示为模式串S2中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.
extend[i]表示为以字符串S1中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.
复杂度:拓展kmp算法的总体复杂度为O(n+m)的。其中n为母串的长度,m为子串的长度。
算法过程:
S2=aaaaab
第一步,我们先对原串S1和模式串S2进行逐一匹配,直到发生不配对的情况。我们可以看到,S1[0]=S2[0],S1[1]=S2[1],S1[2]=S2[2],S1[3] ≠S2[3],此时匹配失败,第一步结束,我们得到S1[0,2]=S2[0,2],即extend[0]=3;
给出n组询问,每一组询问包含两个字符串t s,问s中是否包含t。(t中有’?’,’?’可以代替任何字符)。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<string> 5 #include<algorithm> 6 using namespace std; 7 int l1,l2,n; 8 char s[100009],t[100009]; 9 int nxt[100009]; 10 void next_make() 11 { 12 int t1=0,t2=-1; 13 nxt[0]=-1; 14 while(t1<l1) 15 { 16 if(t2==-1||t[t1]==t[t2]||t[t2]==‘?‘) nxt[++t1]=++t2; 17 else t2=nxt[t2]; 18 } 19 } 20 void match(int t1,int t2) 21 { 22 while(t1<l1&&t2<l2) 23 { 24 if(t1==-1||t[t1]==s[t2]||t[t1]==‘?‘) t1++,t2++; 25 else t1=nxt[t1]; 26 } 27 if(t1==l1) printf("God bless You! "); 28 else printf("Game Over! "); 29 return; 30 } 31 int main() 32 { 33 scanf("%d",&n); 34 while(n--) 35 { 36 cin>>t;cin>>s; 37 l1=strlen(t); 38 l2=strlen(s); 39 next_make(); 40 match(0,0); 41 } 42 return 0; 43 }
Manacher算法:
当我们遇到字符串为“aaaaaaaaa”,之前的算法就会发生各个回文相互重叠的情况,会产生重复计算,然后就产生了一个问题,能否改进?答案是能,1975年,一个叫Manacher发明了Manacher Algorithm算法,俗称马拉车算法,其时间复杂为O(n)。
例题:
给你一个字符串(仅含字母),每个字母有一个价值,把字符串切断成两部分,若其中一部分为回文串,则其价值等于各字母价值和相加;否则为0。总价值为两部分价值相加,求最大价值。
题解:
先用manacher求出回文子串长度,再枚举切割点,若切后子串的中点是回文中心,且回文长度扩展到i点,则这个子串为回文,记录价值。最后输出最大价值。
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 #include<queue> 5 #include<algorithm> 6 using namespace std; 7 const int maxn=5e5+10; 8 char str[maxn*2],s[maxn]; 9 int len,Len[maxn*2],val[maxn],v[30]; 10 void init() 11 { 12 memset(str,0,sizeof(str)); 13 int k=0; 14 str[k++]=‘$‘; 15 for(int i=1;i<=len;++i) 16 { 17 str[k++]=‘#‘; 18 str[k++]=s[i]; 19 } 20 str[k++]=‘#‘; 21 len=k; 22 } 23 void manacher() 24 { 25 Len[0]=0; 26 int sum=0; 27 int id,mx=0; 28 for(int i=1;i<len;++i) 29 { 30 if(i<mx) Len[i]=min(mx-i,Len[2*id-i]); 31 else Len[i]=1; 32 while(str[i-Len[i]]==str[i+Len[i]]) Len[i]++; 33 if(Len[i]+i>mx) 34 { 35 mx=Len[i]+i; 36 id=i; 37 } 38 } 39 } 40 int main() 41 { 42 int t; 43 scanf("%d",&t); 44 while(t--) 45 { 46 for(int i=0;i<26;++i) 47 scanf("%d",&v[i]); 48 scanf("%s",s+1); 49 len=strlen(s+1); 50 for(int i=1;i<=len;++i) 51 { 52 val[i]=val[i-1]+v[s[i]-‘a‘]; 53 } 54 int n=len; 55 init(); 56 manacher(); 57 int ans=0; 58 for(int i = 1; i < n; i++){ 59 int tmp = 0; 60 if(Len[i + 1] - 1 == i){ 61 if(i % 2){ 62 tmp += val[i / 2] * 2 + v[s[(i + 1) / 2] - ‘a‘]; 63 } 64 else{ 65 tmp += val[i / 2] * 2; 66 } 67 } 68 if(Len[i + n + 1] - 1 == n - i){ 69 if((n - i) % 2){ 70 tmp += (val[i + (n - i) / 2] - val[i]) * 2 + v[s[i + (n + 1- i) / 2] - ‘a‘]; 71 } 72 else{ 73 tmp += (val[i + (n - i) / 2] - val[i]) * 2; 74 } 75 } 76 ans = max(ans, tmp); 77 } 78 printf("%d ",ans); 79 } 80 return 0; 81 }
以上是关于KMP && Manacher && 扩展KMP整理的主要内容,如果未能解决你的问题,请参考以下文章
[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher :G - Power Strings POJ - 2406(kmp简单循环节)
kuangbin专题十六 KMP&&扩展KMP HDU3613 Best Reward(前缀和+manacher or ekmp)
HDU 3068 &&HDU 3294 +最长回文串*3—— manacher/扩展KMP/DP
[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher H - Seek the Name, Seek the Fame POJ - 2752(kmp的nex