• 【专题】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;  
                }  
            }  



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


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

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

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

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

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















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

  • 相关阅读:
    python测试开发django(27)--发送html格式邮件
    python测试开发django(26)--发送邮件send_mail
    python测试开发django(25)--表单提交之post修改密码
    python测试开发django(24)--表单提交之post登录案例
    python测试开发django(23)--表单提交之post注册案例
    python测试开发django(22)--表单提交之get请求
    [模拟] Codefroces 1175B Catch Overflow!
    [gcd,灵感] Codeforces 1200C Round Corridor
    [最短路,floyd] Codeforces 1204C Anna, Svyatoslav and Maps
    [最短路,最大流最小割定理] 2019 Multi-University Training Contest 1 Path
  • 原文地址:https://www.cnblogs.com/cyendra/p/3681567.html
Copyright © 2020-2023  润新知