border
border是指字符串最长公共前后缀。
例如:ababa这个字符串,它的前缀是a,ab,aba,abab它的后缀是a,ba,aba,baba。在前缀与后缀中相同的最长为aba,因此aba为它的border。
这里前缀和后缀都不能与原字符串等长。因此,border也不能与原字符串等长。
Next表
定义
Next表是一个存储border的表。Next[i]表示以i结尾的非前缀子串与到i为止的前缀最长匹配长度。即以i结尾的前缀的border。
例如:ababa。Next[0]必然是0。因为以0结尾的前缀为a,它的border不能与原字符串等长,即不能大于一,因此它没有border。接着是Next[1],前缀为ab,这个子串前后缀没有公共部分,因此border也为0,所以Next[1]=0。然后Next[2],前缀aba,border为1,因此Next[2]=1。以此类推,得到此字符串Next表为0 0 1 2 3。
求字符串Next表
若t[Next[i]+1]=t[i+1],则Next[i+1]=Next[i]+1。
此时已知以i结尾的前缀有border,若字符向后一位与border向后一位相等,那么字符向后一位的border必然也加一。
例如当字符串为ababa,i=2时,此时t[i]为第二个'a',t[i+1]为b。这个例子中Next[i+1]=Next[i]+1,即2。
若t[Next[Next[i]]+1]=t[i+1],则Next[i+1]=Next[Next[i]]+1。即若前缀往后一位与前缀border往后一位不相同,则往前找border的border。直到出现相同的为止。因此,则需要不断将Next[Next[i]]套上新的Next[i],直到满足第一个式子。
以下为代码:
void get_next(string t){
int i,x;
for(nxt[1]=x=0,i=2;t[i]!='\0';i++){
while(x&&t[i]!=t[x+1])x=nxt[x];
if(t[i]==t[x+1])x++;nxt[i]=x;
}
}
KMP
问题
给定两个字符串s1,s2,若s1中存在一子串[l,r]与s2完全相同,则称s2在s1中出现一次,位置为l。
求出s2出现的次数及位置。
KMP算法通常用于求解此类一个串在另一个串中出现的问题。
其中s2叫做模式串,s1叫做主串。
算法
首先从主串第一位开始向后遍历。若出现模式串与主串不一样时,寻找已遍历的前缀border,若border,并将模式串已遍历border前缀与主串已遍历border后缀对齐,继续向后遍历。
代码如下:
void kmp(string s,string t){
int i=1,j=0,k=0;
for(;i<=s.size();i++){
while(j&&s[i]!=t[j+1])j=nxt[j];
if(s[i]==t[j+1])j++;
if(!t[j+1])j=nxt[j];
}
}
有了求next表的代码和kmp的代码之后,就可以完成完整的kmp算法了。
KMP模板题AC代码
#include<bits/stdc++.h>
#define TIE ios::sync_with_stdio(false),cin.tie(0),cin.tie(0);
using namespace std;
const int N=1e6+5;
string s1,s2;int nxt[N];
void get_next(string t){
int i,x;
for(nxt[1]=x=0,i=2;t[i]!='\0';i++){
while(x&&t[i]!=t[x+1])x=nxt[x];
if(t[i]==t[x+1])x++;nxt[i]=x;
}
}
void kmp(string s,string t){
int i=1,j=0,k=0;
for(;i<=s.size();i++){
while(j&&s[i]!=t[j+1])j=nxt[j];
if(s[i]==t[j+1])j++;
if(!t[j+1]){cout<<i-j+1<<'\n';j=nxt[j];}
}
}
signed main(){
TIE;cin>>s1>>s2;s1=" "+s1;s2=" "+s2;get_next(s2);kmp(s1,s2);
for(int i=1;i<s2.size();i++)cout<<nxt[i]<<' ';
return 0;
}
题目:无线传输
题目描述
字符串s2循环拼接成一个字符串,给出这个字符串的一个子串s1。求s2最短长度。
思路
这个题就是给出一个字符串子串,求其最小循环节长度。
有一个定理:s最小循环节长度=|s|-border
因此只需要求出s2的border即可。