【多题合集】KMP练习

来源:互联网 发布:网络连接图标是灰色的 编辑:程序博客网 时间:2024/05/22 04:34

写在前面:太弱了,现在才开始做kmp← ←


传送门-P1961
题意:找出字符串中所有循环节次数大于1的前缀子串,输出它们的最小循环节长度与最大循环次数
思路:首先我们要学一门姿势

如果对于next数组中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 ,
则说明字符串循环,而且 循环节长度为: i - next[i] 循环次数为: i / ( i - next[i] )

传送门-姿势及证明
接下来就比较简单了,预处理next数组,然后1-n找对应的前缀子串的循环节次数,大于1就输出答案(实际上就是next[i]>0,使循环节不是它本身)

#include<cstdio>#include<iostream>#include<cstring>using namespace std;int now,id,n,next[1000010];char s[1000010];main(){    scanf("%d",&n);    while (n)    {        scanf("%s",s);        printf("Test case #%d\n",++id);        for (int i=1;i<n;i++)        {            now=next[i];            while (now&&s[now]!=s[i]) now=next[now];            next[i+1]=now+(s[now]==s[i]);        }        for (int i=1;i<=n;i++)        if (next[i]&&i%(i-next[i])==0) printf("%d %d\n",i,i/(i-next[i]));        puts("");        scanf("%d",&n);    }}

传送门-P2406
题意:给定一个字符串,求它的最大循环次数
思路:比1961还简单,因为不用求非全串的子串,只用求全串,处理方法类似,只是次数为1的时候也要输出
代码:

#include<cstdio>#include<iostream>#include<cstring>#include<cstdlib>using namespace std;int now,l,next[1000010];char s[1000010];void work(){    if (s[0]=='.') exit(0);    l=strlen(s);    for (int i=1;i<l;i++)    {        now=next[i];        while (now&&s[now]!=s[i]) now=next[now];        next[i+1]=now+(s[i]==s[now]);    }    if (next[l]==0||l%(l-next[l])) printf("1\n");    else printf("%d\n",l/(l-next[l]));}main(){    while (scanf("%s",s)!=EOF)    work();}

传送门-P2752
题意:给定一个字符串,求出它所有相同的前缀和后缀
思路:处理出next数组后,从next[len]输出,并当前指针跳到next[len],因为next[i]存的就是长度为i的串中前缀和后缀相等的最大值
代码:

#include<cstdio>#include<iostream>#include<cstring>using namespace std;char s[400010];int l,next[400010],ans[400010];void work(){    l=strlen(s);    int now;    for (int i=1;i<l;i++)    {        now=next[i];        while (now&&s[i]!=s[now]) now=next[now];        next[i+1]=now+(s[i]==s[now]);    }    ans[0]=0;    ans[++ans[0]]=l;    now=l;    while (next[now]) ans[++ans[0]]=next[now],now=next[now];    for (int i=ans[0];i>=1;i--) printf("%d%c",ans[i]," \n"[i==1]);}main(){    while (scanf("%s",s)!=EOF)    work();}

传送门-P3461
题意:给定两个字符串,找出第一个字符串在第二个字符串中出现次数
思路:用next数组进行字符串匹配,如果匹配成功,ans++,当前指针返回第一个字符串的最后一位
代码:

#include<cstdio>#include<iostream>#include<cstring>using namespace std;char ch[10010],s[1000010];int t,ans,l1,l2,next[10010];main(){    scanf("%d",&t);    while (t--)    {        ans=0;        scanf("%s%s",ch,s);        l1=strlen(ch);l2=strlen(s);        int now;        for (int i=1;i<l1;i++)        {            now=next[now];            while (ch[i]!=ch[now]&&now) now=next[now];            next[i+1]=now+(ch[i]==ch[now]);        }        now=0;        for (int i=0;i<l2;i++)        {            while (ch[now]!=s[i]&&now) now=next[now];            now+=(ch[now]==s[i]);            if (now==l1) ans++,now=next[now];        }        printf("%d\n",ans);    }}

认真学习算法并尽力理解其本质,是学好OI的基础

1 0
原创粉丝点击