一、KMP
下标都从1开始
给定一个副串fu和一个主串zhu,求副串fu在主串zhu中出现的位置
1.取最长的相等前后缀,保证不漏解
2.通过模式串前后缀的自我匹配的长度,计算next函数,给j指针打一张表,
失配时就跳到next[j]的位置继续匹配
1.创建nex[N]数组(时间复杂度o(lb))
char zhu[N]:代表主串 cin>>(zhu+1)
char fu[N]:代表副串 cin>>(fu+1)
int la:代表主串的长度 la=strlen(zhu+1)
int lb:代表副串的长度 lb=strlen(fu+1)
nex[i]:表示模式串p[1,i]中相等前后缀的最长长度
void getnex()//创建nex[N]数组,用fu串
{
int i,j;
nex[1]=0;
for(i=2,j=0;i<=lb;i++)
{
while(j&&fu[i]!=fu[j+1])
{
j=nex[j];
}
if(fu[i]==fu[j+1])
j++;
nex[i]=j;//回跳
}
}
ps:
无论增加/减少字符有规律(i指针不回退,j指针来回跑)
1.利用双指针,i扫描副串,j扫描前缀
2.nex[1]=0(因为只有一个字符) ,i=2,j=0(初始化)
3.如果fu[i]!=fu[j+1],让j回跳(退而求其次)到能匹配的位置,如果找不到能匹配的位置,j回跳0
4. fu[i]==fu[j+1] ,让j++,指向匹配前缀的末尾
5.nex[i]=j;
2.副串与主串匹配( 时间复杂度o(la))
void kmp()
{
int i,j;
for(i=1,j=0;i<=la;i++)
{
while(j&&zhu[i]!=fu[j+1])
{
j=nex[j];
}
if(zhu[i]==fu[j+1])
j++;
if(j==lb)
{
cout<<i-lb+1<<endl;
}
}
}
ps:
1.利用双指针,i扫描主串,j扫描副串
2.nex[1]=0(因为只有一个字符) i=1,j=0(初始化)
3.如果s[i]!=p[j+1],让j回跳(退而求其次)到能匹配的位置,如果找不到能匹配的位置,j回跳0
4. s[i]==p[j+1] ,让j++
3.模拟nex[N]
例如:aabaabaaaa
nex[1]=0; a
nex[2]=1; aa (a) 1
nex[3]=0; aab 2
nex[4]=1 aaba (a) 3
nex[5]=2 aabaa (aa) 4
nex[6]=3 aabaab (aab) 5
nex[7]=4 aabaaba (aaba) 6
nex[8]=5 aabaabaa (aabaa) 7
nex[9]=2 aa|baaba|aa (aa) 8(n-1);
4.回跳
aab|aabaa|aa
aabaa|baaaa
最初:
nex[2]=1;
nex[3]=0;
nex[4]=1;
nex[5]=2;
nex[6]=3;
nex[7]=4;
i=8 j=5 nex[8]=5;
回跳
i=9,j=2
i=9,j=1
nex[9]=2;
二、相关例题
P3375 【模板】KMP
一、题目要求
题目描述
给出两个字符串 s1 和 s2,若s1 的区间 [l,r] 子串与s2 完全相同,则称 s2 在 s1 中出现了,其出现位置为 l。
现在请你求出 s2 在 s1 中所有出现的位置。
定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2,你还需要求出对于其每个前缀 ′s′ 的最长 border ′t′ 的长度。
输入格式
第一行为一个字符串,即为 s1。
第二行为一个字符串,即为 s2。
输出格式
首先输出若干行,每行一个整数,按从小到大的顺序输出 s2 在 s1 中出现的位置。
最后一行输出 ∣s2∣ 个整数,第 i 个整数表示s2 的长度为 i 的前缀的最长 border 长度。
输入输出样例
输入
ABABABC ABA
输出 #
1 3 0 0 1
说明/提示
样例 1 解释
。
对于 s2 长度为 3 的前缀 ABA
,字符串 A
既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。
数据规模与约定
本题采用多测试点捆绑测试,共有 3 个子任务。
- Subtask 1(30 points):∣�1∣≤15∣s1∣≤15,∣�2∣≤5∣s2∣≤5。
- Subtask 2(40 points):∣�1∣≤104∣s1∣≤104,∣�2∣≤102∣s2∣≤102。
- Subtask 3(30 points):无特殊约定。
对于全部的测试点,保证 1≤∣s1∣,∣s2∣≤10^6,s1,s2 中均只含大写英文字母。
二、思路
可以直接套用模板,最后输出nex[N]数组
三、代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+10;
const int inf=0x3f3f3f3f;
int la,lb;
char zhu[N],fu[N];
int nex[N];
void getnex()//创建nex[N]数组,用fu串
{
int i,j;
nex[1]=0;
for(i=2,j=0;i<=lb;i++)
{
while(j&&fu[i]!=fu[j+1])
{
j=nex[j];
}
if(fu[i]==fu[j+1])
j++;
nex[i]=j;//回跳
}
}
void kmp()
{
int i,j;
for(i=1,j=0;i<=la;i++)
{
while(j&&zhu[i]!=fu[j+1])
{
j=nex[j];
}
if(zhu[i]==fu[j+1])
j++;
if(j==lb)
{
cout<<i-lb+1<<endl;
}
}
}
void solve()
{
cin>>(zhu+1);
cin>>(fu+1);
la=strlen(zhu+1);
lb=strlen(fu+1);
getnex();
kmp();
for(int i=1;i<=lb;i++)
{
cout<<nex[i]<<' ';
}
cout<<endl;
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}
P4391 [BOI2009] Radio Transmission 无线传输
一、题目要求
题目描述
给你一个字符串 s1,它是由某个字符串 s2 不断自我连接形成的(保证至少重复 2 次)。但是字符串 s2 是不确定的,现在只想知道它的最短长度是多少。
输入格式
第一行一个整数 L,表示给出字符串的长度。
第二行给出字符串 s1 的一个子串,全由小写字母组成。
输出格式
仅一行,表示 s2 的最短长度。
输入输出样例
输入
8 cabcabca
输出
3
说明/提示
样例输入输出 1 解释
对于样例,我们可以利用 abcabc 不断自我连接得到abcabcabcabc,读入的 cabcabca,是它的子串。
规模与约定
对于全部的测试点,保证 1<L≤10^6。
二、思路
假设原串是s0,s0经过大于或者等于两次的自我连接得到s,现在需要我们求出原串s0的长度
可以先求出,s串的nex数组,则可以求出[1,strlen(s+1)]中相等前后缀的长度
则原串的长度为n-nex[n](最小周期);
三、代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+10;
const int inf=0x3f3f3f3f;
int n;
char fu[N];
int nex[N];//表示fu串[1,i]中相等前后缀的最长长度
void solve()
{
cin>>n;
int i,j;
cin>>(fu+1);
int lb=strlen(fu+1);
for(i=2,j=0;i<=lb;i++)
{
while(j&&fu[i]!=fu[j+1])
j=nex[j];
if(fu[i]==fu[j+1])
j++;
nex[i]=j;
}
cout<<n-nex[lb]<<endl;
}
signed main()
{
int t=1;
while(t--)
{
solve();
}
return 0;
}