【多题合集】KMP练习

写在前面:太弱了,现在才开始做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的基础

你可能感兴趣的:(【多题合集】KMP练习)