1. 程式人生 > >KMP模板題 P1537

KMP模板題 P1537

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;
}