【专题】KMP

挖坑...

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

一 模板

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

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;  
            }  
        }  



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


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

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

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

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

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















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

原文地址:https://www.cnblogs.com/cyendra/p/3681567.html