• KMP&扩展KMP&Manacher算法基础与习题(第二更)


    KMP&扩展KMP&Manacher算法基础与习题(第一更)

    KMP&扩展KMP&Manacher算法基础与习题(第三更)

    目录

     

    扩展KMP算法讲解

    例题

    A:HDU-2594 Simpsons’ Hidden Talents

    B:HDU-3336 Count the string

    C:HDU-4300 Clairewd’s message

    D:HDU-1238 Substrings

    E:HDU-2328 Corporate Identity

    F:HDU-3374 String Problem:题意

    G:HDU-2609 How many

    H:FZU-1901 Period II


    扩展KMP算法讲解(转自扩展KMP算法

    问题定义:给定两个字符串 S 和 T(长度分别为 n 和 m),下标从 0 开始,定义extend[i]等于S[i]...S[n-1]与 T 的最长相同前缀的长度,求出所有的extend[i]。举个例子,看下表:

    i01234567
    Saaaaabbb
    Taaaaac  
    extend[i]54321000

    为什么说这是 KMP 算法的扩展呢?显然,如果在 S 的某个位置 i 有extend[i]等于 m,则可知在 S 中找到了匹配串 T,并且匹配的首位置是 i。而且,扩展 KMP 算法可以找到 S 中所有 T 的匹配。接下来具体介绍下这个算法。

    算法流程:

    (1)

    如上图,假设当前遍历到 S 串位置 i,即extend[0]...extend[i - 1]这 i 个位置的值已经计算得到。设置两个变量,a 和 p。p 代表以 a 为起始位置的字符匹配成功的最右边界,也就是 "p = 最后一个匹配成功位置 + 1"。相较于字符串 T 得出,S[a...p) 等于 T[0...p-a)

    再定义一个辅助数组int next[],其中next[i]含义为:T[i]...T[m - 1]与 T 的最长相同前缀长度,m 为串 T 的长度。举个例子:

    i012345
    Taaaaac
    next[i]643210

    (2)

    S[i]对应T[i - a],如果i + next[i - a] < p,如上图,三个椭圆长度相同,根据 next 数组的定义,此时extend[i] = next[i - a]

    (3)

    如果i + next[i - a] == p呢?如上图,三个椭圆都是完全相同的,S[p] != T[p - a]T[p - i] != T[p - a],但S[p]有可能等于T[p - i],所以我们可以直接从S[p]T[p - i]开始往后匹配,加快了速度。

    (4)

    如果i + next[i - a] > p呢?那说明S[i...p)T[i-a...p-a)相同,注意到S[p] != T[p - a]T[p - i] == T[p - a],也就是说S[p] != T[p - i],所以就没有继续往下判断的必要了,我们可以直接将extend[i]赋值为p - i

    (5)最后,就是求解 next 数组。我们再来看下next[i]extend[i]的定义:

    • next[i]: T[i]...T[m - 1]与 T 的最长相同前缀长度;
    • extend[i]: S[i]...S[n - 1]与 T 的最长相同前缀长度。

    恍然大悟,求解next[i]的过程不就是 T 自己和自己的一个匹配过程嘛,下面直接看代码。

    模板

    void Getnext(char *str)
    {
        int i=0,j,po,len=strlen(str);
        nex[0]=len;
        while(str[i]==str[i+1]&&i+1<len)
            i++;
        nex[1]=i;
        po=1;
        for(i=2;i<len;i++)
        {
            if(nex[i-po]+i<nex[po]+po)
                nex[i]=nex[i-po];
            else
            {
                j=nex[po]+po-i;
                if(j<0)j=0;
                while(i+j<len&&str[i+j]==str[j])
                    j++;
                nex[i]=j;
                po=i;
            }
        }
    }
    void EXKMP(char *s1,char *s2,int ex[])
    {
        int i=0,j,po,len=strlen(s1),l2=strlen(s2);
        Getnext(s2);
        while(s1[i]==s2[i]&&i<len&&i<l2)
            i++;
        ex[0]=i;
        po=0;
        for(i=1;i<len;i++)
        {
            if(nex[i-po]+i<ex[po]+po)
                ex[i]=nex[i-po];
            else
            {
                j=ex[po]+po-i;
                if(j<0)j=0;
                while(i+j<len&&j<l2&&s1[i+j]==s2[j])
                    j++;
                ex[i]=j;
                po=i;
            }
        }
    }

    例题

    A:HDU-2594 Simpsons’ Hidden Talents:题目主要意思 就是让你找出前面一串字符的前缀和后面字符串的相同的后缀 ,并且打印这个字符串的长度 我的做法就是把两个字符串拼接起来,用KMp算法的NEXT数组可以求相同的前缀后缀,(next[len]即为后缀与前缀相等的长度)但是要注意,求出的长度不应该大于原来的最短字符串的长度,AC代码:

    #include <cstring>
    #include <iostream>
    #include <cstdio>
    #include <fstream>
    #include <algorithm>
    using namespace std;
    const int maxn=1e6+7;
    const int INF=0x3f3f3f3f;
    #define M 50015
    int next1[maxn];
    char str[maxn],mo1[maxn],mo2[maxn];
    int ans;
    void getnext()
    {
        int i=0,j=-1,m=strlen(str);
        while(i<m){
            if(j==-1||str[i]==str[j])
                next1[++i]=++j;
            else
                j=next1[j];
        }
    }
    /*int kmp()
    {
        int i=0,j=0,n=strlen(str),m=strlen(mo);
        while(i<n){
            if(j==-1||str[i]==mo[j])
                i++,j++;
            else
                j=next1[j];
            if(j==m)
                ans++;
        }
        return ans;
    }*/
    int main()
    {
        while(cin>>mo1>>mo2){
            strcpy(str,mo1);
            strcat(str,mo2);
            next1[0]=-1;
            getnext();
            int len=strlen(str);
            int len1=strlen(mo1);
            int len2=strlen(mo2);
            int n=next1[len];
            if(n>=len1||n>=len2){
                if(n==len1&&n==len2)
                    cout<<mo1<<' '<<n<<endl;
                else if(len1<len2)
                    cout<<mo1<<' '<<len1<<endl;
                else
                    cout<<mo2<<' '<<len2<<endl;
            }
            else{
                bool flag=false;
                for(int i=0;i<n;i++){
                    cout<<str[i];
                    flag=1;
                }
                if(flag)
                    cout<<' ';
                printf("%d
    ",n);
            }
        }
    }

    B:HDU-3336 Count the string:这道题目是KMP算法,对Next数组的活用。题意就是输入 一个字符串,判断它的子串从0到i(i<=长度) 在主串出现的次数之和。题中也给出了  abab子串有a,ab,aba,abab  分别在主串出现了2,2,1,1 共6次。题目解法,跟Next数组创建有关系,Next数组查询的时候会用到回溯,这就证明了,你所要找的串,之前出现过,这样就可以根据回溯的次数来计算出现次数了。

    比如题目中的

    序号    0  1  2  3  4

    字符串  a  b  a  b

    next   -1  0  0  1  2

    从j=1开始,1回溯一次 sum+=1,j=2的时候也是一次,sum+=1,j=3与j=4时分别回溯两次,sum+=2,sum+=2。所以总共六次。

    AC代码:

    #include <cstring>
    #include <iostream>
    #include <cstdio>
    #include <fstream>
    #include <algorithm>
    using namespace std;
    const int maxn=2e6+7;
    const int INF=0x3f3f3f3f;
    #define M 50015
    int next1[maxn];
    char str[maxn],mo[maxn];
    int ans;
    void getnext()
    {
        int i=0,j=-1,m=strlen(mo);
        while(i<m){
            if(j==-1||mo[i]==mo[j])
                next1[++i]=++j;
            else
                j=next1[j];
        }
    }
    /*int kmp()
    {
        ans=0;
        int i=0,j=0,n=strlen(str),m=strlen(mo);
        while(i<n){
            if(j==-1||str[i]==mo[j])
                i++,j++;
            else
                j=next1[j];
            if(j==m)
                ans++;
        }
        return ans;
    }*/
    int main()
    {
        int n,t;
        scanf("%d",&t);
        while(t--){
            memset(next1,0,sizeof next1);
            int sum=0;
            scanf("%d %s",&n,mo);
            next1[0]=-1;
            getnext();
            for(int i=1;i<=n;i++){
                int tem=i;
                while(tem){
                    sum++;
                    tem=next1[tem];
                }
            }
            printf("%d
    ",sum%10007);
        }
    }

    C:HDU-4300 Clairewd’s message:一道思维题,给你一张含有26个字母的密码表,对应相应位置的字母a~z。再给你一组不完整的密文+明文,要求输出完整的密文+明文。思路是将密文+明文翻译过来,以原来的密文+明文为主串,翻译后的字符串为模式串,进行扩展KMP匹配。(具体为什么可以好好想想,毕竟思维题),AC代码:

    #include <iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<map>
    using namespace std;
    int T,n,len;
    char s[100010],t[100010],a[28];
    int nexts[100010],extand[100010];
    
    void getnexts(char *t)
    {
        int len =strlen(t);
        int a=0;
        nexts[0]=len;
        while(a<len-1&&t[a]==t[a+1]) a++;
        nexts[1]=a;
        a=1;
        for(int k=2;k<len;k++)
        {
            int p=a+nexts[a]-1,L=nexts[k-a];
            if(k-1+L>=p)
            {
                int j=max((p-k+1),0);
                while(k+j<len&&t[k+j]==t[j]) ++j;
                nexts[k]=j;
                a=k;
            }
            else nexts[k]=L;
        }
    }
    
    void ekmp(char *s,char *t)
    {
        int lens=len,lent=strlen(t),a=0;
        int minlen=min(lens,lent);
        while(a<minlen&&s[a]==t[a]) ++a;
        extand[0]=a;
        a=0;
        for(int k=1;k<lens;k++)
        {
            int p=a+extand[a]-1,L=nexts[k-a];
            if(k-1+L>=p)
            {
                int j=max(p-k+1,0);
                while(k+j<lens&&j<lent&&s[k+j]==t[j]) ++j;
                extand[k]=j;
                a=k;
            }
            else extand[k]=L;
        }
    }
    
    int main()
    {
        cin>>T;
        while(T--)
        {
            char hash[150];
            scanf("%s",a);
            for(int i=0;i<26;i++)
                hash[ a[i] ]='a'+i;
            scanf("%s",s);
            len=strlen(s);
            for(int i=0;i<len;i++)
                t[i]=hash[s[i]];
            getnexts(t);
            ekmp(s,t);
            int pos=len;
            for(int i=0;i<len;i++)
            {
                if(i+extand[i]>=len&&i>=extand[i])
                {
                    pos=i;
                    break;
                }
            }
            for(int i=0;i<pos;i++)
                printf("%c",s[i]);
            for(int i=0;i<pos;i++)
                printf("%c",t[i]);
            printf("
    ");
        }
    }

    D:HDU-1238 Substrings:给您一些区分大小写的字母字符串,找出最大的字符串X的长度,这样就可以找到X或它的逆字符串作为任意给定字符串的子字符串,可以直接暴力枚举,具体看代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <algorithm>
    using namespace std;
    int Next[105];
    void getNext(string w,int len){
        int i = -1,j = 0;
        memset(Next,0,sizeof(Next));
        Next[0] = -1;
        while(j < len){
            if(i == -1 || w[i] == w[j]){
                i++,j++;
                Next[j] = i;
            }
            else
                i = Next[i];
        }
    }
    bool kmp(string w,int m,string s,int n){
        int i = 0,j = 0;
        getNext(w,m);
        while(j < n){
            if(i == -1 || w[i] == s[j])
                i++,j++;
            else
                i = Next[i];
            if(i >= m){
                return true;
            }
        }
        return false;
    }
    int main(){
        int t;
        scanf("%d",&t);
        while(t--){
            string str[102];
            int ans = 0;
            int n,i,j,k;
            scanf("%d",&n);
            for(i = 0; i < n; i++){
                cin >> str[i];
            }
            for(i = 0; i < str[0].length(); i++){
                for(j = i; j < str[0].length(); j++){//j从i开始,长度为1的串也要算
                    string w = str[0].substr(i,j-i+1);
                    for(k = 1; k < n; k++){
                        string rw = w;
                        reverse(w.begin(),w.end());
                        if(!kmp(w,w.length(),str[k],str[k].length())&&!kmp(rw,rw.length(),str[k],str[k].length()))
                            break;
                    }
                    if(k >= n){
                        if(w.length()>ans)
                            ans = w.length();
                    }
                }
            }
            printf("%d
    ",ans);
        }
        return 0;
    }

    E:HDU-2328 Corporate Identity:给出若干个串,求最长公共子串,又是一道暴力枚举题。直接暴力枚举公共字串的开始位置和长度,AC代码:

    #include <cstdio>
    #include <cstdlib>
    #include<cstring>
    #include <algorithm>
    using namespace std;
    char str [20000][20000];
    int next1[20000];
    char temp[20000];
    char sum[20000];
    void getnext(char *s1){
        int j=0,k=-1;
        int len=strlen(s1);
        next1[0]=-1;
        while(j<len){
            if(k==-1||s1[j]==s1[k]){
                ++j;
                ++k;
                if(s1[j]!=s1[k]) next1[j]=k;
                else next1[j]=next1 [k];
            }
            else k=next1[k];
        }
        return ;
    }
    bool kmp(char *s1,char *s2){
        int len1=strlen(s2);
        int len2=strlen(s1);
        getnext(s1);
        int i=0,j=0;
        while(i<len1){
            if(j==-1||s1[j]==s2[i]){
                ++i;
                ++j;
            }
            else j=next1[j];
            if(j==len2)
            return true;
        }
        return false;
    }
    int main()
    {
        int n;
        int i,j,k;
        while(scanf("%d",&n)==1&&n){
            for( i=0;i<n;i++)
                scanf("%s",str[i]);
                int len=strlen(str[0]);
                memset(sum,'',sizeof(sum));
                for(i=0;i<len;i++){
                    int ans=0;
                    for(j=i;j<len;j++){
                        temp[ans]=str[0][j];
                        ans++;
                        temp[ans]='';//注意这里一定要加上,否则出现越界等情况
                        int flag=1;
                        for(k=1;k<n;k++){
                            if(!kmp(temp,str[k])){
                                flag=0;
                                break;
                            }
                        }
                        if(flag){
                            if(strlen(temp)>strlen(sum)){
                                strcpy(sum,temp);
                            }
                            else if(strlen(temp)==strlen(sum)&&strcmp(temp,sum)<0)
                                strcpy(sum,temp);
                        }
                    }
                }
                if(strlen(sum)>0) printf("%s
    ",sum);
                else printf("IDENTITY LOST
    ");
        }
        return 0;
    }

    F:HDU-3374 String Problem:题意:最小最大表示的模板题,可以记住模板,没必要知道原因,想知道也可以一去网上查查。给出多组数据,每组数据给出一个字符串,要求输出这个字符串的最小最大表示的起始位置,然后分别求出在同构串中起始位置的字符出现的次数。思路:最小最大的起始位置直接套用模版即可,然后使用 KMP 的 next 数组求循环节,则次数=长度/循环节长度(循环n次代表可以有n个位置使移动后和最小最大表示相等)。

    循环字符串的最小表示法的问题可以这样描述:对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。由于语言能力有限,还是用实际例子来解释比较容易:设S=bcad,且S’是S的循环同构的串。S’可以是bcad或者cadb,adbc,dbca。而且最小表示的S’是adbc。最大表示则为dbca

    AC代码:

    #include<iostream>
    #include<cstdio>
    #include<string>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define INF 0x3f3f3f3f
    const int N=1000000+5;
    using namespace std;
    int Next[N];
    char str[N];
    void getNext(char p[]){
        Next[0]=-1;
        int len=strlen(p);
        int j=0;
        int k=-1;
     
        while(j<len){
            if(k==-1||p[j]==p[k]){
                k++;
                j++;
                Next[j]=k;
            }
            else{
                k=Next[k];
            }
        }
    }
    int minmumRepresentation(char *str){//最小表示法
        int len=strlen(str);
        int i=0;
        int j=1;
        int k=0;
        while(i<len&&j<len&&k<len){
            int temp=str[(i+k)%len]-str[(j+k)%len];
            if(temp==0)
                k++;
            else{
                if(temp>0)
                    i=i+k+1;
                else
                    j=j+k+1;
                if(i==j)
                    j++;
                k=0;
            }
        }
        return i<j?i:j;
    }
    int maxmumRepresentation(char *str){//最大表示法
        int len=strlen(str);
        int i=0;
        int j=1;
        int k=0;
        while(i<len&&j<len&&k<len){
            int temp=str[(i+k)%len]-str[(j+k)%len];
            if(temp==0)
                k++;
            else{
                if(temp>0)
                    j=j+k+1;
                else
                    i=i+k+1;
                if(i==j)
                    j++;
                k=0;
            }
        }
        return i<j?i:j;
    }
     
     
    int main(){
        while(scanf("%s",str)!=EOF){
            getNext(str);
     
            int n=strlen(str);
            int len=n-Next[n];
     
            int num=1;//数量
            if(n%len==0)
                num=n/len;
     
            int minn=minmumRepresentation(str);//最小表示
            int maxx=maxmumRepresentation(str);//最大表示
     
            printf("%d %d %d %d
    ",minn+1,num,maxx+1,num);
        }
        return 0;
    }

    G:HDU-2609 How many:有n个有01组成的字符串,每个字符串都代表一个项链,那么该字符串就是一个环状的结构,求可以经过循环旋转,最后不同的串有多少个。最小表示法的应用,将每个串用最小表示法表示出来,其中有多少个不一样的即为结果。AC代码:

    #include <bits/stdc++.h>
    #define PI acos(-1.0)
    #define INF 0x3f3f3f3f
    #define MOD 1000000007
    #define EPS 1e-6
    #define N 1123456
    using namespace std;
    int n,m,sum,res,flag;
    char s[101],ss[101];
    int minString(char *s)
    {
        int i=0,j=1,k=0;
        int len=strlen(s);
        while(i<len&&j<len&&k<len)
        {
            if(s[(i+k)%len]==s[(j+k)%len])k++;
            else
            {
                if(s[(i+k)%len]>s[(j+k)%len])i=i+k+1;
                else j=j+k+1;
                if(i==j)j++;
                k=0;
            }
        }
        return i<j?i:j;
    }
    int main()
    {
        int i,j,k,kk,cas,T,t,x,y,z;
        vector<string>sn;
        while(scanf("%d",&n)!=EOF)
        {
            sn.clear();
            for(i=0;i<n;i++)
            {
                scanf("%s",s);
                m=strlen(s);
                t=minString(s);
                for(j=0;j<m;j++)
                    ss[j]=s[(j+t)%m];
                string st(ss);
                sn.push_back(st);
            }
            sort(sn.begin(),sn.end());
            res=1;
            for(i=1;i<n;i++)
                if(sn[i]!=sn[i-1])
                    res++;
            printf("%d
    ",res);
        }
        return 0;
    }

    H:FZU-1901 Period II:题意:给出一个字符串,问可以看作由长度多少的子串循环得到,最后一周期可以不全。思路:这题可以KMP,next数组求公共的子串,取next[len],之前求循环节的知道,len-next[len]就是最短的循环节,依次递归地求next用子串长度减就是子串的最短循环节,加上原来的子串外的那部分就是新的循环节(子串外部分也是子串的循环节)这个在纸上画一画基本都出来了。两者加在一起就是len-nexts[buf],其中buf为依次递归求nexts的值。AC代码:

    #include <iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    char s[2000100];
    int nexts[2000100],sum[2000100];
    int len,n,num;
    void getnexts(char *s)
    {
        int i=0,j=-1;
        nexts[0]=-1;
        while(i<len)
        {
            if(s[i]==s[j]||j==-1)
            {
                nexts[++i]=++j;
            }
            else j=nexts[j];
        }
    }
     
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s);
            len=strlen(s);
            getnexts(s);
            num=0;
            int buf=len;
            while(buf)
            {
                buf=nexts[buf];
                sum[num++]=len-buf;
            }
            printf("Case #%d: %d
    ",i,num);
            for(int j=0;j<num-1;j++)
                printf("%d ",sum[j]);
            printf("%d
    ",sum[num-1]);
        }
    }
  • 相关阅读:
    SQl语句学习笔记(二)
    Adaboost 算法
    降维PCA技术
    scanf 格式化字符串详解
    大小端模式和位域详解(转载)
    推荐系统开源软件列表汇总和点评(转载)
    遗传算法入门(转载)
    大白话解析模拟退火算法(转载)
    机器学习相关——协同过滤(转载)
    python面向对象之单例模式
  • 原文地址:https://www.cnblogs.com/shmilky/p/14089045.html
Copyright © 2020-2023  润新知