您的位置:首页 > 其它

【专题】KMP

2013-09-29 15:27 141 查看
挖坑...

----------------------------

一 模板

----------------------------

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
] 是最长的子串,既是是s的前缀又是s的后缀,同理1 -> f[ f
] 是次短的...依次递归

while (f
>0){
stk.push(f
);
n=f
;
}


POJ 2406 Power Strings

输出最大的n使得s由a重复n次而成。

当 n%(n-f
)==0时,n-f
是s最短的循环节。

if (n%(n-f
)==0){
printf("%d\n",n/(n-f
));
}
else{
printf("1\n");
}


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\n",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
>0&&n%(n-f
)==0) printf("0\n");
else printf("%d\n",n-f
-n%(n-f
));


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
>na或f
>nb时我们要忽略子串s[ 1->f
]。

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\n",f[m]);
}
else{
printf("0\n");
}


----------------------------

三 进阶

----------------------------

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\n");
else printf("%d\n",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
)

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
)==0) tol=n/(n-f
);
printf("Case %d: %d %d %d\n",++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;
}
}


----------------------------

----------------------------

----------------------------

----------------------------

----------------------------

----------------------------

----------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: