一、固定模板
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;
}