一、概念
1 Border
一个字符串的border指的是这个字符串的最长公共前后缀,且border的长度不能等于原字符串的长度 。例如:
a b b a b abbab abbab 的border是 a b ab ab 。
还有一个结论:字符串s的最小循环节长度=|s|-border
2 Next表
N
e
x
t
[
i
]
Next[i]
Next[i] 表示以下标
i
i
i 为结尾的前缀的border的长度。
求解:(对于字符串t)
- 如果 t [ n e x t [ i ] + 1 ] = = t [ i + 1 ] t[next[i]+1]==t[i+1] t[next[i]+1]==t[i+1] ,那么 n e x t [ i + 1 ] next[i+1] next[i+1] 显然应该等于 n e x t [ i ] + 1 next[i]+1 next[i]+1 。
- 否则,我们发现前缀next[i]的border,也即前缀next[next[i]],他同时也是前缀next[i]的后缀,而前缀next[i]又是i的后缀,所以前缀next[next[i]]也是前缀i的后缀。
下面是代码:
int nex[N];
string s;
void get_next(string s){
int i,j;
for(nex[1]=j=0,i=2;s[i];i++){
while(j&&s[i]!=s[j+1]){
j=nex[j];
}
if(s[i]==s[j+1]){
j++;
}
nex[i]=j;
}
}
3 KMP
从1开始比较,发现失配就往后跳。由于失配前全都匹配,所以可以放心大胆的跳。
代码和上面的差别不大:
void KMP(string p,string s){
ll i,j,k=0;
for(int j=0,i=1;s[i];i++){
while(j&&s[i]!=p[j+1]){
j=nex[j];
}
if(s[i]==p[j+1]){
j++;
}
if(!p[j+1]){
cout<<i-j+1;pr;//这里是找到了一种情况
j=nex[j];
}
}
}
二、实践
1 P3375 【模板】KMP
1 题目描述
现在请你求出 s 2 s_2 s2 在 s 1 s_1 s1 中所有出现的位置。
定义一个字符串
s
s
s 的 border 为
s
s
s 的一个非
s
s
s 本身的子串
t
t
t,满足
t
t
t 既是
s
s
s 的前缀,又是
s
s
s 的后缀。
对于
s
2
s_2
s2,你还需要求出对于其每个前缀
s
′
s'
s′ 的最长 border
t
′
t'
t′ 的长度。
2 思路
直接用上面的代码即可
3 代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const ll N=1e6+2;
ll nex[N];
string s,p;
ll cnt;
void get_next(string s){
int i,j;
for(nex[1]=j=0,i=2;s[i];i++){
while(j&&s[i]!=s[j+1]){
j=nex[j];
}
if(s[i]==s[j+1]){
j++;
}
nex[i]=j;
}
}
void KMP(string p,string s){
ll i,j,k=0;
for(int j=0,i=1;s[i];i++){
while(j&&s[i]!=p[j+1]){
j=nex[j];
}
if(s[i]==p[j+1]){
j++;
}
if(!p[j+1]){
cout<<i-j+1;pr;
j=nex[j];
}
}
}
int main(){
cin>>s>>p;
s=' '+s;p=' '+p;
get_next(p);
KMP(p,s);
for(int i=1;i<p.size();i++){
cout<<nex[i]<<" ";
}
return 0;
}
2 终曲(加强版)
1 题目描述
给出三个字符串,请说出第一串的某个子串,要求该子串长度最小,并且同时包含第2个串和第3个串。
特别地,如果有多个这样的子串,则请输出字母序最小的一个。
2 思路
使用KMP,计算字符串s
中子串p1
和p2
的匹配位置,分别存储在vl1
、vr1
、vl2
、vr2
中。
遍历第一个串的所有匹配位置,对于每个匹配位置,使用二分查找在第二个串的匹配位置中找到合适的位置,更新答案的区间。
3 代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const ll N=2e5+8;
ll nex[N];
ll ansl,ansr=INF;
vector<pair<ll,ll> > rg;
char s[N],p1[102],p2[102];
vector<ll> vl1,vr1,vl2,vr2;
string ans;
void getans(string s){
if(ans.size()==0||ans>s){
ans=s;
}
}
void update(ll l,ll r){
if(ansr-ansl>r-l){
ansr=r,ansl=l;
rg.clear();
rg.push_back(make_pair(l,r));
}
else if(ansr-ansl+1==r-l+1){
rg.push_back(make_pair(l,r));
}
}
void get_next(char s[]){
int i,j;
nex[i]=0;
for(int i=2,j=0;s[i];i++){
while(j&&s[i]!=s[j+1]){
j=nex[j];
}
if(s[i]==s[j+1]){
j++;
}
nex[i]=j;
}
}
void KMP(char s[],char p[],vector<ll>&vl,vector<ll>&vr){
ll n=strlen(p+1);
get_next(p);
ll i,j,sum=0;
for(j=0,i=1;s[i];i++){
while(j&&s[i]!=p[j+1]){
j=nex[j];
}
if(s[i]==p[j+1]){
j++;
}
if(!p[j+1]){
vl.push_back(i-n+1);
vr.push_back(i);
j=nex[j];
}
}
}
int main(){
scanf("%s%s%s",s+1,p1+1,p2+1);
KMP(s,p1,vl1,vr1);
KMP(s,p2,vl2,vr2);
ll n=vl1.size(),m=vl2.size();
if(n==0||m==0){//简单的优化
printf("No");
return 0;
}
for(int i=0;i<n;i++){
//第一个比l1大的r2
ll idx=lower_bound(vr2.begin(),vr2.end(),vl1[i])-vr2.begin();
if(idx!=m){
update(min(vl1[i],vl2[idx]),max(vr1[i],vr2[idx]));
}
//第一个比l1小的r2
idx--;
if(idx>=0){
update(min(vl1[i],vl2[idx]),max(vr1[i],vr2[idx]));
}
//本来还应有两种情况,但因本题数据较弱,仅用前两种即可。
}
ll len=rg.size();
for(int i=0;i<len;i++){
char t=s[rg[i].second+1];
s[rg[i].second+1]='\0';
getans(string(s+rg[i].first));
s[rg[i].second+1]=t;
}
cout<<ans;
return 0;
}