8.2 kmp 扩展kmp
Posted raincle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8.2 kmp 扩展kmp相关的知识,希望对你有一定的参考价值。
假设一母串S,子串P
KMP:用于求解子串P在母串S中第一次出现的位置,或是在母串S中出现的次数。(最长公共前缀后缀)
next数组的含义:next[i]表示前面长度为i的子串中,前缀和后缀相等的最大长度。
拓展kmp是对KMP算法的扩展,它解决如下问题:(最长公共前缀)
定义母串S,和子串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extend[i](0<=i<n)。
注意到,如果有一个位置extend[i]=m,则表示T在S中出现,而且是在位置i出现,这就是标准的KMP问题,所以说拓展kmp是对KMP算法的扩展,所以一般将它称为扩展KMP算法。
next数组的含义:next[i]表示子串T与T[i,m-1](T的第i个后缀)的最长公共前缀(与上面的定义类似,就是以子串代母串)
KMP求最小循环节、循环周期:https://blog.csdn.net/hao_zong_yin/article/details/77455285
定理:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。
(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。
(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。
定理可以这么理解:
对于一个字符串,如abcd abcd abcd,由长度为4的字符串abcd重复3次得到,那么必然有原字符串的前八位等于后八位。
也就是说,对于某个字符串S,长度为len,由长度为L的字符串s重复R次得到,当R≥2时必然有S[0..len-L-1]=S[L..len-1],字符串下标从0开始
那么对于KMP算法来说,就有next[len]=len-L。此时L肯定已经是最小的了(因为next的值是前缀和后缀相等的最大长度,即len-L是最大的,那么在len已经确定的情况下,L是最小的)。
A题:
Description
Input
Output
Sample Input
ababcababababcabab
aaaaa
Sample Output
2 4 9 18
1 2 3 4 5
HINT
此题就是求母串S的公共前缀后缀值(跟子串没关系)
设len=strlen(S),next[len]等于长度为len的子串(母串S本身)的最长公共前缀后缀值,所以枚举长度从1到next[len]的前缀后缀,如果相等就记录长度即可。
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10; char str[maxn*4]; int Next[400010]; void getnext(char p[]) { int plen=strlen(p); Next[0]=-1; int j=0,k=-1; while(j<plen) { //p[k]表示前缀,p[j]表示后缀 if(k==-1||p[j]==p[k]) { j++; k++; //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]] Next[j]=k; } else k=Next[k]; } } int main() { while(~scanf("%s",str)) { getnext(str); int l=strlen(str); int m=Next[l]; int i,j; string a="",b=""; for(i=0,j=l-1;i<m;i++,j--) { a=a+str[i]; b=str[j]+b; if(a==b)printf("%d ",i+1); } printf("%d ",l); } return 0; }
B题:
Description
Input
Output
Sample Input
abcd
aaaa
ababab
.
Sample Output
1
4
3
HINT
这题就是一个求循环节的题目,相同的子串即为母串的循环节
用len-next[len]求出循环节的长度
用len/(len-next[len])即可得到有几个相同的子串
#include <bits/stdc++.h> using namespace std; const int maxn=1e6+10; char str[maxn]; int next[maxn]; void getnext(char *s,int next[]) { int plen=strlen(s); next[0]=-1; int j=0,k=-1; while(j<=plen-1) { //p[k]表示前缀,p[j]表示后缀 if(k==-1||s[j]==s[k]) { j++; k++; //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]] next[j]=k; } else k=next[k]; } } int main() { while(1) { scanf("%s",str); memset(next,0,sizeof next); if(str[0]==‘.‘)break; getnext(str,next); int l=strlen(str); int ans=1; if(l%(l-next[l])==0)//不整除就没有循环节 { ans=l/(l-next[l]); } printf("%d ",ans); } return 0; }
以上是关于8.2 kmp 扩展kmp的主要内容,如果未能解决你的问题,请参考以下文章