挖坑...
----------------------------
一 模板
----------------------------
const int maxn=1111111; char P[maxn]; char T[maxn]; int f[maxn]; void getFail(char P[],int f[]){ int i=0,j=-1; int len=strlen(P); f[0]=-1; while (i<len){ if (j==-1||P[i]==P[j]){ i++,j++; f[i]=j; } else{ j=f[j]; } } } void KMP(char T[],char P[],int f[]){ int i=0,j=0; int n=strlen(T); int m=strlen(P); getFail(P,f); while(i<n){ if(j==-1||T[i]==P[j]){ i++,j++; } else{ j=f[j]; } if(j==m){ // TO DO: //ans++; j=f[j]; } } }
----------------------------
二 练习
----------------------------
POJ 3461 Oulipo
计算单词W在整篇文章T中出现的次数。
KMP最基本的应用,统计出现次数,套模板即可。
if(j==m){ // TO DO: ans++; j=f[j]; }
POJ 2752 Seek the Name, Seek the Fame
找到一个S的子串作为前缀-后缀字符串。所谓前缀-后缀字符串即S的子串不仅是S的前缀又是S的后缀。
子串s[ 1 -> f[n] ] 是最长的子串,既是是s的前缀又是s的后缀,同理1 -> f[ f[n] ] 是次短的...依次递归
while (f[n]>0){ stk.push(f[n]); n=f[n]; }
POJ 2406 Power Strings
输出最大的n使得s由a重复n次而成。
当 n%(n-f[n])==0时,n-f[n] 是s最短的循环节。
if (n%(n-f[n])==0){ printf("%d ",n/(n-f[n])); } else{ printf("1 "); }
POJ 1961 Period
对每个前缀i,若能由某些字符重复k次形成,输出最大的k。
与上题类似,枚举i,若i%(i-f[i])==0 则最短循环节为i-f[i],k为i/(i-f[i])
for (int i=2;i<=n;i++){ if (f[i]>0&&i%(i-f[i])==0){ printf("%d %d ",i,i/(i-f[i])); } }
HDU 3336 Count the string
求出s有多少个子串是它本身的前缀。
DP公式如下。
for (int i=1;i<=n;i++){ dp[i]=dp[f[i]]+1; ans=(ans+dp[i])%10007; }
HDU 3746 Cyclic Nacklace
至少要在字符串s后面补几个字符才能凑成一个循环。
若本身已经有循环节,则答案为0。
if (f[n]>0&&n%(n-f[n])==0) printf("0 "); else printf("%d ",n-f[n]-n%(n-f[n]));
HDU 2087 剪花布条
给定T和P,为T中能分出几块P。
只匹配一次的KMP。当匹配成功时将j置为0即可。
if(j==m){ // TO DO: ans++; j=0; }
HDU 2594 Simpsons’ Hidden Talents
求a的最长前缀是b的后缀。
将两串拼接成s,a在前b在后,则问题转化为求一个串的前缀是后缀。
注意s的前缀不一定是a的前缀也不一定是b的后缀,所以当f[n]>na或f[n]>nb时我们要忽略子串s[ 1->f[n] ]。
while (f[m]>n1||f[m]>n2){ m=f[m]; } if (f[m]>0){ for (int i=0;i<f[m];i++){ printf("%c",s1[i]); } printf(" %d ",f[m]); } else{ printf("0 "); }
----------------------------
三 进阶
----------------------------
hdu 4763 Theme Section
求出满足EAEBE格式的最长子串E的长度。
由最长前缀后缀推广而来。
首先由大到小枚举前缀后缀,对于每个前缀后缀f[x],在字符串中间寻找f[i]=f[x],若找到则输出答案,否则继续枚举。
int x=n; bool flag=false; while (f[x]>(n/3)) x=f[x]; while (f[x]>0){ flag=false; for (int i=f[x]*2;i<=n-f[x];i++){ if (f[i]==f[x]){ flag=true; break; } } if (flag) break; } if (!flag) printf("0 "); else printf("%d ",f[x]);
----------------------------
四 扩展KMP
----------------------------
extand[i]中储存的是T的后缀串i -> len 和模式串P的最长公共前缀。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int MM=100005; int next[MM],extand[MM]; char S[MM],T[MM]; void GetNext(const char *T){ int len=strlen(T),a=0; next[0]=len; while(a<len-1 && T[a]==T[a+1]) a++; next[1]=a; a=1; for(int k=2;k<len;k++){ int p=a+next[a]-1,L=next[k-a]; if( (k-1)+L >= p){ int j = (p-k+1)>0 ? (p-k+1) : 0; while(k+j<len && T[k+j]==T[j]) j++; next[k]=j; a=k; } else next[k]=L; } } void GetExtand(const char *S,const char *T){ GetNext(T); int slen=strlen(S),tlen=strlen(T),a=0; int MinLen = slen < tlen ? slen : tlen; while(a<MinLen && S[a]==T[a]) a++; extand[0]=a; a=0; for(int k=1;k<slen;k++){ int p=a+extand[a]-1, L=next[k-a]; if( (k-1)+L >= p){ int j= (p-k+1) > 0 ? (p-k+1) : 0; while(k+j<slen && j<tlen && S[k+j]==T[j]) j++; extand[k]=j; a=k; } else extand[k]=L; } } int main(){ while(scanf("%s%s",S,T)==2){ GetExtand(S,T); for(int i=0;i<strlen(T);i++) printf("%d ",next[i]); puts(""); for(int i=0;i<strlen(S);i++) printf("%d ",extand[i]); puts(""); } return 0; }
----------------------------
五 扩展KMP练习
----------------------------
1 HDU 4333 Revolving Digits
读入数字串P,T由两个P拼接而成。
则T从0到n的每个长度为n的后缀即为一种数字排列。
对于T的后缀i,设其与原数字P的最长公共前缀长度为L。
若L>=n,说明此后缀表示的数与原数字相等。
若L<n,则令 T[i+extand[i]] 与 P[extand[i]] 比较大小即可得出两数的大小。
对于类似123123形式的重复串,排列三次以后又回到了123123的形式,所以答案必须除以循环节。
用KMP的找到最小循环节个数n/(n-f[n])
scanf("%s",P); strcpy(T,P); strcat(T,P); GetExtand(T,P); int n=strlen(P); int cnt1=0,cnt2=0,cnt3=0; for (int i=0;i<n;i++){ if (extand[i]>=n) cnt2++; else if (T[i+extand[i]]<P[extand[i]]) cnt1++; else cnt3++; } getFail(P,f); int tol=1; if (n%(n-f[n])==0) tol=n/(n-f[n]); printf("Case %d: %d %d %d ",++Cas,cnt1/tol,cnt2/tol,cnt3/tol);
2 HDU 4300 Clairewd’s message
给一个密文到明文的映射表
给一个串,前面为密文,后面为明文,密文一定是完整的,但明文不完整或没有
将这个串补全。
令原串为T,将原串全部翻译为P。
可以发现原串T的后缀i是P的前缀。
从(n+1)/2开始枚举T的后缀,对于每个后缀i,若i+extand[i]>=n则从T:0~i-1为密文,P:i~n-1为明文。
GetExtand(T,P); int ret=len; for (int i=(len+1)/2;i<len;i++){ if (extand[i]+i>=len){ ret=i; break; } }
----------------------------
----------------------------
----------------------------
----------------------------
----------------------------
----------------------------
----------------------------