poj 2406 poj 1961 個人對吉大KMP模板的理解 KMP 基礎題--找週期串
好佩服寫kmp程式碼的人... 看死了終於看得有點明白了......
學kmp先看兩個比較好的總結
這兩個講解非常好
摘一句我認為最重要的話
預處理出這樣一個數組P[j],表示當匹配到B陣列的第j個字母而第j+1個字母不能匹配了時,新的j最大是多少。P[j]應該是所有滿足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
(B陣列就是模式串)額,似乎難以理解,那我就摘抄下matrix67的話吧:
假如,A="abababaababacb",B="ababacb",我們來看看KMP 是怎麼工作的。我們用兩個指標i和j分別表示,A[i-j+ 1..i]與B[1..j]完全相等。也就是說,i是不斷增加的,隨著i的增加j相應地變化,且j滿足以A[i]結尾的長度為j的字串正好匹配B串的前 j個字元(j當然越大越好),現在需要檢驗A[i+1]和B[j+1]的關係。當A[i+1]=B[j+1]時,i和j各加一;什麼時候j=m了,我們就 說B是A的子串(B串已經整完了),並且可以根據這時的i值算出匹配的位置。當A[i+1]<>B[j+1],KMP的策略是調整j的位置 (減小j值)使得A[i-j+1..i]與B[1..j]保持匹配且新的B[j+1]恰好與A[i+1]匹配(從而使得i和j能繼續增加)。我們看一看當 i=j=5時的情況。
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 56 7
此時,A[6]<>B[6]。這表明,此時j不能等於5了,我們要把j改成比它小的值j'。j'可能是多少呢?仔細想一下,我們發現,j'必須 要使得B[1..j]中的頭j'個字母和末j'個字母完全相等(這樣j變成了j'後才能繼續保持i和j的性質)。這個j'當然要越大越好。在這裡,B[1..5]="ababa",頭3個字母和末3個字母都是"aba"。而當新的j為3時,A[6]恰好和B[4]相等。於是,i變成了6,而j則變成了 4:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a cb
j = 1 2 3 4 5 67
從上面的這個例子,我們可以看到,新的j可以取多少與i無關,只與B串有關。我們完全可以預處理出這樣一個數組P[j],表示當匹配到B陣列的第j個字母而第j+1個字母不能匹配了時,新的j最大是多少。P[j]應該是所有滿足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
再後來,A[7]=B[5],i和j又各增加1。這時,又出現了A[i+1]<>B[j+1]的情況:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a b a cb
j = 1 2 3 4 5 67
由於P[5]=3,因此新的j=3:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a ba c b
j = 1 2 3 45 6 7
這時,新的j=3仍然不能滿足A[i+1]=B[j+1],此時我們再次減小j值,將j再次更新為P[3]:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a ba b a c b
j = 1 23 4 5 6 7
現在,i還是7,j已經變成1了。而此時A[8]居然仍然不等於B[j+1]。這樣,j必須減小到P[1],即0:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a b a c b
j = 0 12 3 4 5 6 7
終於,A[8]=B[1],i變為8,j為1。事實上,有可能j到了0仍然不能滿足A[i+1]=B[j+1](比如A[8]="d"時)。因此,準確的說法是,當j=0了時,我們增加i值但忽略j直到出現A[i]=B[1]為止。
我用的是吉大的程式碼(似乎寫法跟主流不太一樣?):
int fail[P];
int kmp(char* str, char* pat)
{
int i, j, k;
memset(fail, -1, sizeof(fail));
for(i = 1; pat[i]; ++i)
{
for(k=fail[i-1]; k>=0 && pat[i]!=pat[k+1];k=fail[k]);
if(pat[k + 1] == pat[i]) fail[i] = k + 1;
}
i = j = 0;
while( str[i] && pat[j] )
{// By Fandywang
if( pat[j] == str[i] ) ++i, ++j;
else if(j == 0)++i;//第一個字元匹配失敗,從str下個字元開始
else j = fail[j-1]+1;
}
if( pat[j] )return -1;
else return i-j;
}
說了這麼多,全是抄的別人的...現在該談我自己的理解了:
KMP程式碼做了兩件事:一是處理模式串(有用pat,pattern,B表示)整理出fail陣列(也有說next陣列,matrix67說的是P);
二是從原串中查詢模式串;
重複一下那句最重要的話:預處理出這樣一個數組P[j],表示當匹配到B陣列的第j個字母而第j+1個字母不能匹配了時,新的j最大是多少。P[j]應該是所有滿足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
如fail[i]=k;則從pat[0]到pat[i]一共i+1個字元,fail[i]=k意味著從fail[0]到fail[i-1]這i個字元0到k-1這前k個字元形成的子串與i-k到i-1這k個字元形成的子串相同,如abcdabcd,fail[5]=1,就是“abcda”,pat[0]==pat[4],所以是1,
這個例子跟poj 2406還不是很接近,那麼再看一個例子:
abcabcabc 它的fail[8]=5,前5個字元為"abcab",後5個字元為“abcab”,但因為下標從0開始,並且原串就是由一個子串重複多次得到的,所以可以斷言,pat[8]==pat[5],就是說前6個字元和後六個字元相同,再看看,原串長度9-6剛好等於一個週期 pe=len-fail[len-1]-1;那麼週期數就是len/pe;
貼程式碼吧:
//141MS
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000003
int fail[N];
char str[N];
void KMP()
{
int len=strlen(str);
int i=0,k;
memset(fail,-1,sizeof(fail));
/*
k=-1;
while(i<len)
{
if(k==-1||str[i]==str[k])
fail[++i]=++k;
else
k=fail[k];
}
i=len-k;//如果最後一個位置不匹配,那麼就會滾到len-k的位置,也就是最小重複字串的長度。
if(len%i==0)
return len/i;
else
return 1;
*/
for(i=1;str[i];i++)
{
for(k=fail[i-1];k>=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
}
int pe=len-fail[len-1]-1;
if(len%pe==0)printf("%d\n",len/pe);
else printf("1\n");
}
int main()
{
while(scanf("%s",str)!=EOF)
{
if(!strcmp(str,"."))break;
KMP();
}
return 0;
}
註釋掉的是另一種寫法...懶得去學了....
知道這個題再去看poj 1961 就很容易做了;
題意是,字首如果是週期串,找出它由幾個週期組成,典型的poj 2406翻版,不過是檢測原串的從0到i形成的字串是不是週期串(i<=len-1)
貼程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000003
int fail[N],len;
char str[N];
void kmp()
{
int i,k;
memset(fail,-1,sizeof(fail));
for(i=1;i<len+1;i++)
{
for(k=fail[i-1];k>=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
int t=fail[i];
if((i+1)%(i-t)==0&&(i+1)/(i-t)>1)printf("%d %d\n",i+1,(i+1)/(i-t));
}
}
int main()
{
//freopen("in.txt","r",stdin);
int ncase=1;
while(scanf("%d",&len),len)
{
printf("Test case #%d\n",ncase++);
scanf("%s",str);
kmp();
putchar('\n');
}
return 0;
}
如果還是不明白kmp,建議模擬,多列印fail陣列,k的值,看規律,依據那句最重要的話理解就好