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",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\n",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("\n");
    }
}

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\n",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,'\0',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]='\0';//注意这里一定要加上,否则出现越界等情况
                    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\n",sum);
            else printf("IDENTITY LOST\n");
    }
    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\n",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\n",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\n",i,num);
        for(int j=0;j<num-1;j++)
            printf("%d ",sum[j]);
        printf("%d\n",sum[num-1]);
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值