8.2 kmp 扩展kmp

假设一母串S,子串P

KMP:用于求解子串P在母串S中第一次出现的位置,或是在母串S中出现的次数。(最长公共前缀后缀)

next数组的含义:next[i]表示前面长度为i的子串中,前缀和后缀相等的最大长度。

拓展kmp是对KMP算法的扩展,它解决如下问题:(最长公共前缀)

定义母串S,和子串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extend[i](0<=i<n)。

注意到,如果有一个位置extend[i]=m,则表示T在S中出现,而且是在位置i出现,这就是标准的KMP问题,所以说拓展kmp是对KMP算法的扩展,所以一般将它称为扩展KMP算法。

next数组的含义:next[i]表示子串T与T[i,m-1](T的第i个后缀)的最长公共前缀(与上面的定义类似,就是以子串代母串)

KMP求最小循环节、循环周期:https://blog.csdn.net/hao_zong_yin/article/details/77455285

定理:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。

(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。

(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。

定理可以这么理解:

对于一个字符串,如abcd abcd abcd,由长度为4的字符串abcd重复3次得到,那么必然有原字符串的前八位等于后八位。

也就是说,对于某个字符串S,长度为len,由长度为L的字符串s重复R次得到,当R≥2时必然有S[0..len-L-1]=S[L..len-1],字符串下标从0开始

那么对于KMP算法来说,就有next[len]=len-L。此时L肯定已经是最小的了(因为next的值是前缀和后缀相等的最大长度,即len-L是最大的,那么在len已经确定的情况下,L是最小的)。

A题:

Description

 HHM要给自己的小狗起名字,他是这样做的,他先找到一个字符串S,字符串S所有前缀与后缀相同的字串,都可以作为名字。现在他想知道所有符合条件的字串的长度。

Input

有多组输入,每一组输入包含一个字符串S(1<=length of S <=400000)

Output

输出所有可能的子串长度,中间以空格结束。最后一个数字后不要有空格。

Sample Input

ababcababababcabab
aaaaa

Sample Output

2 4 9 18
1 2 3 4 5

HINT

此题就是求母串S的公共前缀后缀值(跟子串没关系)

设len=strlen(S),next[len]等于长度为len的子串(母串S本身)的最长公共前缀后缀值,所以枚举长度从1到next[len]的前缀后缀,如果相等就记录长度即可。

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
char str[maxn*4];
int Next[400010];
void getnext(char p[])
{
    int plen=strlen(p);
    Next[0]=-1;
    int j=0,k=-1;
    while(j<plen)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            Next[j]=k;
        }
        else k=Next[k];
    }
}
int main()
{
    while(~scanf("%s",str))
    {
        getnext(str);
        int l=strlen(str);
        int m=Next[l];
        int i,j;
        string a="",b="";
        for(i=0,j=l-1;i<m;i++,j--)
        {
            a=a+str[i];
            b=str[j]+b;
            if(a==b)printf("%d ",i+1);
        }
        printf("%d
",l);
    }
    return 0;
}
View Code

B题:

Description

HHM碰到了这样一个问题,给出一个字符串,问它最多由多少相同的子串组成

Input

每个测试数据输入一个字符串s,s(1<=len(s)<=1000000)。输入以.结束

Output

输出最多有多少个相同的子串组成。

Sample Input

abcd
aaaa
ababab
.

Sample Output

1
4
3

HINT

这题就是一个求循环节的题目,相同的子串即为母串的循环节

用len-next[len]求出循环节的长度

用len/(len-next[len])即可得到有几个相同的子串

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
char str[maxn];
int next[maxn];
void getnext(char *s,int next[])
{
    int plen=strlen(s);
    next[0]=-1;
    int j=0,k=-1;
    while(j<=plen-1)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||s[j]==s[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            next[j]=k;
        }
        else k=next[k];
    }
}
int main()
{
    while(1)
    {
        scanf("%s",str);
        memset(next,0,sizeof next);
        if(str[0]=='.')break;
        getnext(str,next);
        int l=strlen(str);
        int ans=1;
        if(l%(l-next[l])==0)//不整除就没有循环节
        {
            ans=l/(l-next[l]);
        }
        printf("%d
",ans);
    }
    return 0;
}
View Code

D题:hdu4333

最近HHM对数字有兴趣
对于一个数字,他可以将几个最后的数字放在整数的前面。当然,他可以将所有数字放在前面,这样他就可以获得整数。
例如,他可以将123更改为312,231和123.
现在他想知道他可以得到多少不同于整数的整数,他可以得到多少不同的整数等于原始整数以及如何他可以获得的许多不同的整数大于原始整数。
我们将确保原始整数是正的并且它没有前导零,但是如果我们通过旋转数字得到带有一些前导零的整数,我们将认为新的整数没有前导零。例如,如果原始整数是104,我们可以得到410,41和104。

Input

输入的第一行包含整数T(1 <= T <= 50),表示测试用例的数量。
对于每个测试用例,只有一行是原始整数N.我们将确保N是一个正整数,不带前导零,N小于10 ^ 100000

Output

输出格式如下: Case X: L E G  X表示测试组数,L表示得到的比原数字小的个数, E表示等于原数字的个数,G表示大于原数字的个数

Sample Input

1
341

Sample Output

Case 1: 1 1 1

HINT

注意字符串总是将最后几个数字放到前面,我们可以一个数字一个数字地放,这样放几次后与一次将几个数放到前面的结果相同

如 1234->4123->3412->2341->1234(最后总是回到原状)

我们考虑将字符串复制一次

12341234

从第一个数字开始看到原数字的位数个数字  1234

从第二个....2341

3412

4123

每一个状态都对应我们一个一个地放数字

如何比较两个字符串的大小

找到它们第一个不相同的位置比较位置上的元素大小即可

如此我们不难想到要算出他们的最长公共前缀长度

于是我们把复制后的串当做母串S,原串当做子串T,对他俩做扩展KMP,算出T与S的每一个后缀的最长公共前缀

若extend[i]==m,证明这个变化后的串与T相等

若不等(肯定小于),我们比较S[i+extend[i]]与T[extend[i]]的大小即可

因为T可能会有循环节,导致结果有重复,所以结果要除以循环节的个数

例如  1212           2121 1212 2121 1212(有两个是重复的)

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;   //字符串长度最大值
int Next[maxn],ex[maxn],Next2[maxn]; //ex数组即为extend数组
//预处理计算next数组
char str1[maxn*2],str2[maxn];
void GETNEXT(char *str)
{
    int i=0,j,po,len=strlen(str);
    Next[0]=len;//初始化next[0]
    while(str[i]==str[i+1]&&i+1<len)//计算next[1]
    i++;
    Next[1]=i;
    po=1;//初始化po的位置
    for(i=2;i<len;i++)
    {
        if(Next[i-po]+i<Next[po]+po)//第一种情况,可以直接得到next[i]的值
        Next[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到next[i]的值
        {
            j=Next[po]+po-i;
            if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
            while(i+j<len&&str[j]==str[j+i])//计算next[i]
            j++;
            Next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    GETNEXT(s2);//计算子串的next数组
    while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
    i++;
    ex[0]=i;
    po=0;//初始化po的位置
    for(i=1;i<len;i++)
    {
        if(Next[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
        ex[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到ex[i]的值
        {
            j=ex[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
            j++;
            ex[i]=j;
            po=i;//更新po的位置
        }
    }
}
void getnext(char p[])
{
    int plen=strlen(p);
    Next2[0]=-1;
    int j=0,k=-1;
    while(j<plen)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            Next2[j]=k;
        }
        else k=Next2[k];
    }
}
int main()
{
    int t;
    cin>>t;
    int d=1;
    while(t--)
    {
        scanf("%s",str2);
        strcpy(str1,str2);
        strcat(str1,str2);
        EXKMP(str1,str2);
        int l1=strlen(str1),l2=strlen(str2);
        int i,ans1=0,ans2=0,ans3=0;
        for(i=0;i<l2;i++)
        {
            if(ex[i]>=l2)ans2++;
            else
            {
                if(str1[i+ex[i]]<str2[ex[i]])ans1++;
                else ans3++;
            }
        }
        getnext(str2);
        int l=l2-Next2[l2],res=1;
        if(l2%res==0)res=l2/l;
        printf("Case %d: %d %d %d
",d,ans1/res,ans2/res,ans3/res);
        d++;
    }

    return 0;
}
View Code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;   //字符串长度最大值
int Next[maxn],ex[maxn],Next2[maxn]; //ex数组即为extend数组
//预处理计算next数组
char str1[maxn*2],str2[maxn];
void GETNEXT(char *str)
{
    int i=0,j,po,len=strlen(str);
    Next[0]=len;//初始化next[0]
    while(str[i]==str[i+1]&&i+1<len)//计算next[1]
    i++;
    Next[1]=i;
    po=1;//初始化po的位置
    for(i=2;i<len;i++)
    {
        if(Next[i-po]+i<Next[po]+po)//第一种情况,可以直接得到next[i]的值
        Next[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到next[i]的值
        {
            j=Next[po]+po-i;
            if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
            while(i+j<len&&str[j]==str[j+i])//计算next[i]
            j++;
            Next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    GETNEXT(s2);//计算子串的next数组
    while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
    i++;
    ex[0]=i;
    po=0;//初始化po的位置
    for(i=1;i<len;i++)
    {
        if(Next[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
        ex[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到ex[i]的值
        {
            j=ex[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
            j++;
            ex[i]=j;
            po=i;//更新po的位置
        }
    }
}
void getnext(char p[])
{
    int plen=strlen(p);
    Next2[0]=-1;
    int j=0,k=-1;
    while(j<plen)
    {       //p[k]表示前缀,p[j]表示后缀
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因为不能出现p[j]=p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
            Next2[j]=k;
        }
        else k=Next2[k];
    }
}
int main()
{
    int t;
    cin>>t;
    int d=1;
    while(t--)
    {
        scanf("%s",str2);
        strcpy(str1,str2);
        strcat(str1,str2);
        EXKMP(str1,str2);
        int l1=strlen(str1),l2=strlen(str2);
        int i,ans1=0,ans2=0,ans3=0;
        for(i=0;i<l2;i++)
        {
            if(ex[i]>=l2)ans2++;
            else
            {
                if(str1[i+ex[i]]<str2[ex[i]])ans1++;
                else ans3++;
            }
        }
        getnext(str2);
        int l=l2-Next2[l2],res=1;
        if(l2%res==0)res=l2/l;
        printf("Case %d: %d %d %d
",d,ans1/res,ans2/res,ans3/res);
        d++;
    }

    return 0;
}
View Code
原文地址:https://www.cnblogs.com/raincle/p/9409782.html