KMP中next实现模板及讲解+习题练习 一条龙【C++】

一、固定模板

void kmpn(int *next){                          //求解kmp中next数组
     for(int i=2,j=0;i<=n;i++){                //上方p从2开始,下方从1开始,遍历上方p
        while(j>0&&p[i]!=p[j+1])j=next[j];     //只要不匹配,j持续前移至0
        if(p[i]==p[j+1])j++;                   //如果匹配,j向后移一位
        next[i]=j;                             //每次记录当前i位置的最大相同前后缀长度
    }
}

        注意:模式串、匹配串的下标都从1开始。 

 二、讲解

         对于Kmp,还是比较容易忘记的,一种记忆方法就是形象化记忆:

        不匹配,一直退;

        若匹配,向前进;

        遍历 i ,晃动 j.     

        依旧可以通过模拟进行理解求解next数组的过程:

                        0   0   0   1   2   3   1      

        i               a   b   c   a   b   c   a

        j                    a   b   c   a   b   c   a

         可以根据模板代码进行推演。值得注意的是:我们是将模式串p进行了对自己的串匹配,i从2开始,因为从1开始没有意义,完全匹配。还有,我们将i与j+1相比,因为j是从0开始的。

三、习题

        1.斤斤计较的小Z

        

很明显,Kmp,当然,stl的find也可以做 ,注意它的第二个测试用例反了。

find代码(不清楚用法的可以参考算法竞赛小知识汇总(C++)): 

#include <bits/stdc++.h>
using namespace std;

int main()
{
  string s1,s2;
  cin>>s2>>s1;
  int len=0,ans=0;
  while((len=s1.find(s2,len))!=string::npos){
    len+=s2.length();
    ans++;
  }
  cout<<ans;
  return 0;
}

Kmp代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;

char s1[N],s2[N];
int ans;
int len1,len2;

void kmp(int *next){
    for(int i=2,j=0;i<=len1;i++){
        while(j&&s2[i]!=s2[j+1])j=next[j];
        if(s2[i]==s2[j+1])j++;
        next[i]=j;
    }
}
int main()
{
    string a,b;
    cin>>a>>b;              
    int len1=a.length(),len2=b.length();  //或者strlen(char数组)
    for(int i=1;i<=len1;i++)s1[i]=a[i-1];
    for(int i=1;i<=len2;i++)s2[i]=b[i-1];
    
    int next[N];
    kmp(next);

    for(int i=1,j=0;i<=len2;i++){
      while(j&&s2[i]!=s1[j+1])j=next[j];
      if(s2[i]==s1[j+1])j++;
      if(j==len1)ans++;
    }
    cout<<ans;
    return 0;
}

        2.幸运字符串

知道next数组求解的过程就知道是Kmp,遍历一遍求最大next值(模板,不再给出代码) 

         3.boarder

 主要在于求出相同字串的单个长度,理解最长相同前后缀,可见注释

 代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+9;
char s[N];
int n;

void kmp(int *next){
    for(int i=2,j=0;i<=n;i++){
        while(j&&s[i]!=s[j+1])j=next[j];
        if(s[i]==s[j+1])j++;
        next[i]=j;
    }
}

int main()
{
  cin>>s+1;
  n=strlen(s+1);
  int next[N];
  kmp(next);
  int t=n-next[n];                            //单个重复字符串长度=总长-最长相同前后缀长度
  //cout<<n<<" "<<next[n]<<" "<<t<<endl;      //如:abcabcabc ——> 3=9-6
  if(t!=n&&n%t==0) cout<<n/t;                 //如果不是单独一个字符串
  else cout<<1;
}

        4.无线传输

 这道题样例给的是  cabcabca  ,结果为3 。

        对于很多问题,往往需要先发现,后证明。

        思路:这道题你认为组成这个字符串的s2是谁?abc还是cab又或者是bca?都可以,因为题目说了只想知道最短长度,至于它到底是什么无关紧要。那我们看如何求?我们要思考:它始终是由同一个字符串组成的,那是不是相同前后缀很长?我们会通过例子发现最后的next[n]始终最长。那剩下的一小段字符串(n-next)是不是就是答案了呢?经过它的堆叠是否可以拼成s1呢?其实做题时可以通过举例来验证。具体详细证明可参考:洛谷第一个题解

 代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;

int n;
char s1[N];

void kmpn(int *next){
    for(int i=2,j=0;i<=n;i++){
        while(j&&s1[i]!=s1[j+1])j=next[j];
        if(s1[i]==s1[j+1])j++;
        next[i]=j;
    }
}
int main(){
    cin>>n;
    cin>>s1+1;
    int next[N];
    kmpn(next);
    cout<<n-next[n];
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值