首先附上代碼ios
1 void GetNext(char* p,int next[]) 2 { 3 int pLen = strlen(p); 4 next[0] = -1; 5 int k = -1; 6 int j = 0; 7 while (j < pLen - 1) 8 { 9 //p[k]表示前綴,p[j]表示後綴 10 if (k == -1 || p[j] == p[k]) 11 { 12 ++k; 13 ++j; 14 next[j] = k; 15 } 16 else 17 { 18 k = next[k]; 19 } 20 } 21 }
首先咱們得明白,next[j]是表示除了p[j]以外,0~j-1這個串,前綴和後綴的最大匹配長度api
由於咱們下標是從0開始,因此這個最大匹配長度也就是,知足最長的前綴和後綴匹配後的前綴的下一個位置,數組
因此next數組知足以下性質app
對於0~jide
p[j-1]=p[next[j]-1],爲何呢,由於next[j]表示,和以p[j-1]爲最後一個字符的後綴匹配的最長前綴的下一個位置,ui
那麼0~next[j]這個串最後的位置的前一個位置必定和後綴的最後一個位置p[j-1]匹配,因此p[next[j]-1]=p[j-1]this
那麼對於0~next[j]idea
p[next[j]-1]=p[next[next[j]]-1]spa
next[next[j]]表示,和以p[next[j]-1]爲最後一個字符的後綴匹配後的最長前綴的下一個位置,(最長的意思就是下一個位置必定不匹配,若是下一個位置匹配,那麼這個就不是最長),那麼對於0~next[next[j]],它最後一個位置的前一個位置必定和後綴的最後一個位置p[next[j]-1]相等設計
因而
p[next[next[j]-1]]=p[next[j]-1]
因而咱們能推出什麼呢
p[j-1]=p[next[j]-1]=p[next[next[j]]-1]
也就是說記最初的位置爲j,迭代若干次next數組(j=next[j])獲得j'
必定知足p[j-1]=p[j'-1]
另外對於上面的代碼,首先咱們分析每一個位置的狀態
若是它的位置爲0,那麼當不匹配的時候它將陷入一個自環,不斷地只能和第一個0位置匹配,因此咱們設計
0->-1->0 這樣的自環狀態
那麼對於位置不爲0的地方,有兩種狀況
找不到相同的前綴和後綴,若是當前的位置爲j,則說明此時沒有能匹配的知足以p[j-1]爲最後一個字符的後綴,那麼怎麼辦呢,那就只能暴力回溯
到第0個位置
能找到相同的前綴後綴
此時又分兩種狀況,能匹配上
若是能匹配上則,下一個位置的next就等於如今的k+1,由於此時的k其實等於next[j],若是p[j]==p[next[j]],那麼next[j+1]=next[j]+1
由於next[j]沒有算p[j],此時若是再次匹配則說明先後綴公共長多應當多加上表明新加進來的字符p[j]的一個長度
詳細一點說就是當前的最長匹配前綴從0~next[j]-1,變成了0~next[j]
不能匹配上
咱們令j=next[j],由上面的結論推知p[j-1]=p[j'-1],因此咱們能獲得長度不斷遞減的,能匹配以p[j-1]爲最後一個字符的後綴
若是k回溯到-1,那說明確實沒有相同的先後綴,放棄以前保留的匹配長度,直接將位置回溯到0
實際上不省略內在邏輯的求Next數組的程序是
void initNext(){ Next[1]=0; int p1=0,p2=1; while(p2<=m){ if(p1==0){ //此時說明它連第一個字符都匹配不了,那麼後續的匹配應當讓前綴的指針停留在第一個字符位置1 //以p2爲結尾的後綴沒有匹配的前綴 Next[p2+1]=1;//那麼當它的下一位失配應當比較第一位 //這裏的含義很是特殊,由於第1位以前的串是空串,也即其實符合了Next數組的定義,空串的最長先後匹配是0 //比較第一位也就意味着,第一位以前的空串的最大先後匹配是0 //此處也表示最長先後匹配爲0時,只能將指針回溯到第一位,也即第一位尚未匹配,等待匹配的狀態 p1=1;//回溯指針,p1懸浮在可能擴展的最後一個字符 p2++;//考慮計算以p2+1結尾的最大匹配 } else if(b[p1]==b[p2]){ //當前綴和後綴有一個字符匹配 Next[p2+1]=Next[p2]+1;//這個轉移表示從1~Next[p2]-1的匹配串延拓到1~Next[p2] p1=Next[p2]+1;//p1要移動到當前可能擴展的最後一個字符 p2++;//考慮計算以p2+1結尾的最大匹配 } else{ //第一次進入這個狀態時,或者連續這個狀態迭代的第一次,當前的p1其實就等於Next[p2] //Next[p2]表示,1~Next[p2]-1,b[Next[p2]-1]=b[p2-1]; //p1=Next[p1]就等價於p1=Next[Next[p2]]; //仍然能獲得一個b[Next[Next[p2]]-1]=b[Next[p2]-1]=b[p2-1]的前綴 p1=Next[p1];//不斷迭代到長度遞減前綴,符合匹配最後一個字符是b[p1-1] } } }
而且值得咱們注意的是,
指針p1懸浮在的位置說明該位置狀態不肯定,須要匹配一下確認
p1=minIndex-1時則表示,連第一個字符都匹配不到,應當走自環,讓指針p1再回到minIndex,由於此時minIndex位置待匹配
長度爲len的串,則長度爲len的前綴和後綴必定相等就是它自己,此時最大匹配長度不計數,由於它是沒有意義的
好比a,其實它有前綴a,後綴a但因爲長度爲len因此不計數
因此前綴指針p1,後綴指針p2,初始的時候p2-p1=1也即相鄰,也就是說初始最小長度必定是2,一個在前一個在後
hdu1711求模式串在文本串中出現的最先位置
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=1e5+7; const int N=1e6+7; int Next[maxn],a[N],b[maxn],n,m; void initNext(){ memset(Next,0,sizeof(Next)); Next[1]=0; int p1=0,p2=1; while(p2<=m){ if(p1==0){ //此時說明它連第一個字符都匹配不了,那麼後續的匹配應當讓前綴的指針停留在第一個字符位置1 //以p2爲結尾的後綴沒有匹配的前綴 Next[p2+1]=1;//那麼當它的下一位失配應當比較第一位 //這裏的含義很是特殊,由於第1位以前的串是空串,也即其實符合了Next數組的定義,空串的最長先後匹配是0 //比較第一位也就意味着,第一位以前的空串的最大先後匹配是0 //此處也表示最長先後匹配爲0時,只能將指針回溯到第一位,也即第一位尚未匹配,等待匹配的狀態 p1=1;//回溯指針,p1懸浮在可能擴展的最後一個字符 p2++;//考慮計算以p2+1結尾的最大匹配 } else if(b[p1]==b[p2]){ //當前綴和後綴有一個字符匹配 Next[p2+1]=Next[p2]+1;//這個轉移表示從1~Next[p2]-1的匹配串延拓到1~Next[p2] p1=Next[p2]+1;//p1要移動到當前可能擴展的最後一個字符 p2++;//考慮計算以p2+1結尾的最大匹配 } else{ //第一次進入這個狀態時,或者連續這個狀態迭代的第一次,當前的p1其實就等於Next[p2] //Next[p2]表示,1~Next[p2]-1,b[Next[p2]-1]=b[p2-1]; //p1=Next[p1]就等價於p1=Next[Next[p2]]; //仍然能獲得一個b[Next[Next[p2]]-1]=b[Next[p2]-1]=b[p2-1]的前綴 p1=Next[p1];//不斷迭代到長度遞減前綴,符合匹配最後一個字符是b[p1-1] } } } int Match(){ int i,j;i=1;j=1; while(i<=n){ if(j==0){ i++;j++; } else if(a[i]==b[j]){ i++;j++; } else j=Next[j]; if(j==m+1) return i-j+1; } return -1; } int main(){ int T;scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) scanf("%d",a+i); for(int i=1;i<=m;++i) scanf("%d",b+i); initNext(); //for(int i=1;i<=m;++i) printf("nxt:%d,",Next[i]);printf("\n"); printf("%d\n",Match()); } return 0; }
可是其實這個程序是不對的,Next[p2+1]=Next[p2]+1;這個轉移不對
真正的轉移是 Next[p2]=i+1;此時i纔是若干次迭代後的Next[p2']
因此對於poj1961,上面的求法就WA了,能過數據只是數據太水
Time Limit: 3000MS | Memory Limit: 30000K | |
Total Submissions: 17771 | Accepted: 8562 |
Description
Input
Output
Sample Input
3 aaa 12 aabaabaabaab 0
Sample Output
Test case #1 2 2 3 3 Test case #2 2 2 6 2 9 3 12 4
Source
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=1e6+7; char s[maxn];int n,Next[maxn]; int main(){ int cas=0; while(~scanf("%d",&n)){ if(n!=0&&cas) printf("\n"); if(n==0) break; printf("Test case #%d\n",++cas); scanf("%s",s+1); memset(Next,-1,sizeof(Next)); Next[1]=0;int i=0,j=1; while(j<=n){ if(i==0){ Next[j+1]=1;//1 not 0 i=1;j++; if(Next[j]!=-1&&j>=2&&j<=n&&j%(j-Next[j])==0&&s[j]==s[Next[j]]){ printf("%d %d\n",j,j/(j-Next[j])); } } else if(s[i]==s[j]){ Next[j+1]=i+1;// i++;j++; if(Next[j]!=-1&&j>=2&&j<=n&&j%(j-Next[j])==0&&s[j]==s[Next[j]]){ printf("%d %d\n",j,j/(j-Next[j])); } } else { i=Next[i];//not j=Next[j] } } } return 0; }
另外,有一個邏輯錯,週期串不必定是2的倍數
Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 48116 | Accepted: 20030 |
Description
Input
Output
Sample Input
abcd aaaa ababab .
Sample Output
1 4 3
Hint
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int maxn=1e6+7; char s[maxn];int Next[maxn]; int main(){ while(~scanf("%s",s+1)){ int len=strlen(s+1); if(len==1){ if(s[1]=='.') break; printf("1\n");continue; } memset(Next,-1,sizeof(Next)); Next[1]=0;int p1=0,p2=1; while(p2<=len){ if(p1==0){ Next[++p2]=1; p1=1; } else if(s[p1]==s[p2]){ Next[++p2]=p1+1; p1++; } else p1=Next[p1]; } if(Next[len]==1){ printf("1\n");continue; } if((len%(len-Next[len])==0)&&s[len]==s[Next[len]]){ printf("%d\n",len/(len-Next[len])); } else printf("1\n"); } return 0; }
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 40122 | Accepted: 16122 |
Description
The French author Georges Perec (1936–1982) once wrote a book, La disparition, without the letter 'e'. He was a member of the Oulipo group. A quote from the book:
Tout avait Pair normal, mais tout s’affirmait faux. Tout avait Fair normal, d’abord, puis surgissait l’inhumain, l’affolant. Il aurait voulu savoir où s’articulait l’association qui l’unissait au roman : stir son tapis, assaillant à tout instant son imagination, l’intuition d’un tabou, la vision d’un mal obscur, d’un quoi vacant, d’un non-dit : la vision, l’avision d’un oubli commandant tout, où s’abolissait la raison : tout avait l’air normal mais…
Perec would probably have scored high (or rather, low) in the following contest. People are asked to write a perhaps even meaningful text on some subject with as few occurrences of a given 「word」 as possible. Our task is to provide the jury with a program that counts these occurrences, in order to obtain a ranking of the competitors. These competitors often write very long texts with nonsense meaning; a sequence of 500,000 consecutive 'T's is not unusual. And they never use spaces.
So we want to quickly find out how often a word, i.e., a given string, occurs in a text. More formally: given the alphabet {'A', 'B', 'C', …, 'Z'} and two finite strings over that alphabet, a word W and a text T, count the number of occurrences of W in T. All the consecutive characters of W must exactly match consecutive characters of T. Occurrences may overlap.
Input
The first line of the input file contains a single number: the number of test cases to follow. Each test case has the following format:
Output
For every test case in the input file, the output should contain a single number, on a single line: the number of occurrences of the word W in the text T.
Sample Input
3 BAPC BAPC AZA AZAZAZA VERDI AVERDXIVYERDIAN
Sample Output
1 3 0
kmp統計子串出現的次數,注意當p1>t_len的時候,若是s串中沒有空字符,那麼咱們就至關於考慮,(注意Next數組算到maxIndex+1
T的最後一個空字符和S不匹配,那麼說明T以前的字符和S都匹配了,直接走p1=Next[p1]是正確的,直接p1=1是錯的,會少算答案
好比AZAZAZA,AZA,當第二個串跑到4的時候用Next能夠跳到2,能夠繼續匹配中間的AZA,而直接回溯一,就只能直接算最後一個AZA
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=1e6+7; char S[maxn],T[maxn]; int Next[maxn]; int main(){ int t;scanf("%d",&t); while(t--){ scanf("%s%s",T+1,S+1); memset(Next,-1,sizeof(Next)); int s_len=strlen(S+1),t_len=strlen(T+1); Next[1]=0;int p1=0,p2=1; while(p2<=t_len){ if(p1==0){ Next[++p2]=1;p1=1; } else if(T[p1]==T[p2]){ Next[++p2]=++p1; } else p1=Next[p1]; } int ans=0; p1=p2=1; while(p2<=s_len){ if(p1==0||T[p1]==S[p2]){ p1++;p2++; if(p1>t_len){ ans++;//這裏不用回溯p1指針 } } else { p1=Next[p1]; } } printf("%d\n",ans); } return 0; }
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 8622 Accepted Submission(s): 3707
注意輸出答案時對於Next[len]=1的特判
abca,abce的區分
以及xyzabcabcqe這個是無週期的
只要前面出現了後綴,後面出現了前綴,那麼咱們能夠只補右面,直接從左往右找週期串,沒必要把中間的週期串摳出來
好比exyzabcabcqe,qexyzabcabcqe,qexyzabcabcqex,xyzabcabcqex,xyzabcabcqexy等
以及週期串的長度必定是len-Next[len],注意len-Next[len]=1的特判和Next[len]=1的特判
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int maxn=1e5+7; char s[maxn];int Next[maxn]; int main(){ int T;scanf("%d",&T); while(T--){ scanf("%s",s+1); memset(Next,-1,sizeof(Next)); Next[1]=0;int p1=0,p2=1,len=strlen(s+1); while(p2<=len){ if(p1==0){ Next[++p2]=1;p1=1; } else if(s[p1]==s[p2]){ Next[++p2]=++p1; } else p1=Next[p1]; } int mod=len-Next[len]; if(Next[len]==1){ if(s[len]==s[1]) printf("%d\n",len-2);//週期串長度自己變成len-1,在減去已經有的結尾,len-1-1 else printf("%d\n",len); continue; } if(mod==1){ if(s[len]==s[Next[len]]) printf("0\n"); else printf("%d\n",len); } else{ if(len%mod==0) printf("0\n"); else printf("%d\n",mod-len%mod); } } return 0; }