KMP模板題 P1537
阿新 • • 發佈:2018-11-30
Description
給出兩個字串P和T(僅由大寫字母構成),請你計算字串P在T中出現的次數。
Input
第一行一個整數n,表示資料組數,每組資料包含兩行,第一行是字串P,第二行是字串T。
Output
對於每組資料輸出一個整數,表示P在T中出現的次數。
Hint
1<=|P|<=10 000|P|<={T|<=1 000 000
Solution
P是模式串,T是主串,定義i,j兩個假指標,用i指向主串,用j指向模式串,普通演算法是通過strlen得到兩個串的長度後,遇到不匹配的地方,i,j都需要回退,時間效率為O(mn),為了提高效率,使得i不回退,只有j這個指標回退,那麼就需要求得P這個模式串的最大字首與字尾,使得當遇到不匹配的地方時j只需要回退到最大字首的最後一個位置,而i就不需要回退,只需要繼續++。實現這個過程就需要一個fail陣列用來存放當每個j遇到與i不匹配時,將要回退到什麼地方。
首先因為Input有多組資料,所以每一次程式開始的時候最好進行清零(ql函式)。
輸入T,P兩個字串,在setfail之前用strlen得到兩個串的長度,此時用k,j兩個假指標指向模式串,當1這個位置不匹配時fail應該回退到0這個位置,將fail[0]初始化為-1,那麼當1這個位置不匹配時就會++,此時fail[1]=0,當k與j不匹配且k>=0時,即k還可以回退的時候,每次不匹配k就進行回退直到k,j匹配,而j只需要++,並將fail[j]賦值為k,那麼每次i,j不匹配時,j=fail[j],j就會回退到最大字首的最後一個位置。
而kmp的過程與setfail的過程非常相似,只是指向的是兩個陣列,i指向T,j指向P,由於問題求的是P在T中出現的次數,需要用一個cnt來記錄出現的次數。每到不匹配的時候j就進行回退(回退只需要j=fail[j]就可以回退),否則i++,j++。每到j全部匹配完一次就將cnt++,然後再次把j回退到fail[j]就可以了。
注意事項:
1.輸入T和P串時是從0位置開始輸入的所以strlen得到的字元長度多了1,迴圈的時候不需要取等。
2.在將i和j進行跳動的時候i和j要同時++。
3.習慣每次輸出資料打\n。
4.setfail之後要把j清零。
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define maxn 1000001 using namespace std; char T[maxn],P[maxn]; int fail[maxn]; int i,j,k,m,n,u,cnt; void ql(){ memset(T,0,sizeof(T)); memset(P,0,sizeof(P)); memset(fail,0,sizeof(fail)); i=j=m=n=cnt=0; } void init(){ scanf("%s",P); scanf("%s",T); } void setfail(){ m=strlen(T); n=strlen(P); fail[0]=-1; k=-1;j=0; for(;j<n;){ while(P[k]!=P[j]&&k>=0){ k=fail[k]; } k++; j++; fail[j]=k; } } void kmp(){ j=0; for(int i=0;i<m;){ while(P[j]!=T[i]&&j>=0){ j=fail[j]; } i++; j++; if(j==n){ cnt++; j=fail[j]; } } } int main(){ scanf("%d",&u); for(int p=1;p<=u;p++){ ql(); init(); setfail(); kmp(); printf("%d\n",cnt); } return 0; }