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
HHM要給自己的小狗起名字,他是這樣做的,他先找到一個字符串S,字符串S所有前綴與後綴相同的字串,都可以作為名字。現在他想知道所有符合條件的字串的長度。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\n",l); } return 0; }View Code
B題:
Description
HHM碰到了這樣一個問題,給出一個字符串,問它最多由多少相同的子串組成Input
每個測試數據輸入一個字符串s,s(1<=len(s)<=1000000)。輸入以.結束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\n",ans); } return 0; }View Code
8.2 kmp 擴展kmp