KMP演算法的經典例題(poj 3461、poj 2752、poj 2406、poj1961)
阿新 • • 發佈:2018-12-24
最簡單的KMP題,找出第一個字串在第二個字串中出現次數。
#include <iostream> #include <cstdio> #include <cstring> #define Memset(x, a) memset(x, a, sizeof(x)) using namespace std; const int N=1e6+10; char w[N],t[N]; int next[N]; int sum; void getNext(const char P[],int next[]){ int m=strlen(P); int i=0,j; j=next[0]=-1; while(i<m){ while(-1!=j && P[i]!=P[j])j=next[j]; next[++i]=++j; } } void kmp(const char T[],const char P[],int next[]){ int n=strlen(T),m=strlen(P); int i,j; getNext(P,next); i=j=0; while(i<n){ while(-1!=j && T[i]!=P[j])j=next[j]; i++;j++; if(j>=m){ sum++; j=next[j];//這兒修改很重要,不然會超時 } } } int main(){ int T; scanf("%d",&T); while(T--){ sum=0; Memset(next,0); scanf("%s%s",w,t); kmp(t,w,next); printf("%d\n",sum); } return 0; }
思路:其實是next陣列的使用
下面給出描述: (i>1)[下標從0開始]
next[i]的意義就是:前面長度為i的字串的【字首和字尾的最大匹配長度】
那麼這題怎麼利用這個性質呢?
詳細分析一下:【就用上面的第一個例子說明吧】
求出next值:[非修正] |
下標: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
串: a b a b c a b a b a b a b c a b a b |
next值: -1 0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9 |
len2 = 18 next[len2] = 9
說明對於前面長度為18的字串,【長度為9的字首】和【長度為9的字尾】是匹配的, 即上圖的藍色跟紅色匹配
也就是整個串的最大前後綴匹配長度就是9了
所以接下來根本不需要考慮長度大於9的情況啦
好了!既然現在只需考慮長度小於9的前後綴匹配情況,那麼
[問題就轉化成藍色串的字首跟紅色串的字尾的匹配問題了!!!
又因為藍串==紅串
所以問題又轉化成
找藍串自己的字首跟自己的字尾的最大匹配了!!!
那麼我們現在就要找next[9]的值了【next[9]的含義:藍串的最大前後綴匹配】
回憶第一步:我們找的是next[len2]=9【len2=18】
怎麼使得第二部目標變成9【求next[9]】呢?
其實next[len2]=9同時可以表示為:最大匹配前後綴的【字首長度】
那麼next[9]的意義就是:
【主串】的最大匹配前後綴的【字首】的【最大匹配前後綴】了!!
也就是上面藍串的前後綴最大匹配長度了!!
那麼演算法描述就是:
第一步:求next[len2], 即next[18] = 9;
第二步:把9代進來,即求next[9] = 4;
第三步:把4代進來,即求next[4] = 2;
第四步:next[2] = 0; 也就是下標2之前的串已經沒有前後綴可以匹配了
所以答案就是: 2 4 9 18 【PS: 從小到大輸出,18是串長,顯然符合題意】
#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using namespace std;
const int N=4e5+10;
int next[N],ans[N];
char s[N];
void getNext(const char P[],int next[]){
int m=strlen(P);
int i=0,j;
j=next[0]=-1;
while(i<m){
while(-1!=j && P[i]!=P[j])j=next[j];
next[++i]=++j;
}
}
int main(){
while(~scanf("%s",s)){
Memset(next,0);
getNext(s,next);
int cnt=0;
int len=strlen(s);
int j=next[len];
while(j>0){
ans[++cnt]=j;
j=next[j];
}
for(int i=cnt; i>0; i--)printf("%d ",ans[i]);
printf("%d\n",len);
}
return 0;
}
思路:KMP,next表示模式串如果第i位(設str[0]為第0位)與文字串第j位不匹配則要回到第next[i]位繼續與文字串第j位匹配。則模式串第1位到next[n]與模式串第n-next[n]位到n位是匹配的。所以思路和上面一樣,如果n%(n-next[n])==0,則存在重複連續子串,長度為n-next[n]。
例如:a b a b a b
next:-1 0 0 1 2 3 4
next[n]==4,代表著,字首abab與字尾abab相等的最長長度,這說明,ab這兩個字母為一個迴圈節,長度=n-next[n];
#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using namespace std;
const int N=1e6+10;
int next[N];
char s[N];
void getNext(const char P[],int next[]){
int m=strlen(P);
int i=0,j;
j=next[0]=-1;
while(i<m){
while(-1!=j && P[i]!=P[j])j=next[j];
next[++i]=++j;
}
}
int main(){
while(~scanf("%s",s)){
if(s[0]=='.')break;
Memset(next,0);
getNext(s,next);
int len=strlen(s);
if(len%(len-next[len])==0)printf("%d\n",len/(len-next[len]));
else printf("1\n");
}
return 0;
}
題意:
給你一個字串,求這個字串到第i個字元為止的迴圈節的次數。
比如aabaabaabaab,長度為12.到第二個a時,a出現2次,輸出2.到第二個b時,aab出現了2次,輸出2.到第三個b時,aab出現3次,輸出3.到第四個b時,aab出現4次,輸出4.
思路:
這道題就是上題的加強版而已。上一道題輸出一個字串的迴圈節出現的次數,這個是到第i個字元為止,其實就是多了一層迴圈。把這個字串遍歷一次即可,具體思路也以參考小白書的例題。
#include <iostream>
#include <cstdio>
#include <cstring>
#define Memset(x, a) memset(x, a, sizeof(x))
using namespace std;
const int N=1e6+10;
char s[N];
int next[N];
int n;
void getNext(const char P[],int next[]){
int m=strlen(P);
int i=0,j;
j=next[0]=-1;
while(i<m){
while(-1!=j && P[i]!=P[j])j=next[j];
next[++i]=++j;
}
}
int main(){
int kase=0;
while(~scanf("%d",&n)&&n){
scanf("%s",s);
Memset(next,0);
getNext(s,next);
printf("Test case #%d\n",++kase);
for(int i=2; i<=n; i++){
if(next[i]>0&&i%(i-next[i])==0)printf("%d %d\n",i,i/(i-next[i]));
}
printf("\n");
}
return 0;
}