P3375 【模板】KMP
题目描述
给出两个字符串 s 1 s_1 s1 和 s 2 s_2 s2,若 s 1 s_1 s1 的区间 [ l , r ] [l, r] [l,r] 子串与 s 2 s_2 s2 完全相同,则称 s 2 s_2 s2 在 s 1 s_1 s1 中出现了,其出现位置为 l l l。
现在请你求出 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′ 的长度。
输入格式
第一行为一个字符串,即为 s 1 s_1 s1。
第二行为一个字符串,即为 s 2 s_2 s2。
输出格式
首先输出若干行,每行一个整数,按从小到大的顺序 输出 s 2 s_2 s2 在 s 1 s_1 s1 中出现的位置。
最后一行输出 ∣ s 2 ∣ |s_2| ∣s2∣ 个整数,第 i i i 个整数表示 s 2 s_2 s2 的长度为 i i i 的前缀的最长 border 长度。
输入输出样例 #1
输入 #1
ABABABC
ABA
输出 #1
1
3
0 0 1
说明/提示
样例 1 解释
。
对于 s 2 s_2 s2 长度为 3 3 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1 1 1。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+1;
int kmp[N];//所有子串的最长相同前后缀长度
string s1,//文本串
s2;//模式串
int main(){
//freopen("data.cpp","r",stdin);
cin>>s1>>s2;
memset(kmp,0,sizeof(kmp));
for(int i=1,j=0;i<s2.length();i++){
while(j&&s2[i]!=s2[j])j=kmp[j];
/*
i长字符串后第一个字符,就是i位置字符s2[i]
不等于
i长字符串的最长相同前后缀j长子串后第一个字符,就是j位置字符s2[j]
回溯
找i长字符串的最长相同前后缀的最长相同前后缀
*/
if(s2[i]==s2[j])j++;//如果相等,则最长相同前后缀要+1
kmp[i+1]=j;//i+1字符串的相同前后缀长是j
}
for(int i=0,j=0;i<s1.length();){
if(s1[i]==s2[j])i++,j++;//对齐了,各自找下一位
else if(j<s2.length())//没匹配
if(j>0)j=kmp[j];//模式串的对齐位置跳到j长字符串的最长相同前后缀后的第一个位置j
else i++;
if(j==s2.length()){cout<<i-j+1<<endl;j=kmp[j];}//匹配后输出s2在i中的位置
}
for(int i=0;i<s2.length();i++)cout<<kmp[i+1]<<" ";
return 0;
}

小结
文本串的i位置和匹配串的j位置对齐。