题目描述
给出两个字符串
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 长度。
算法内容
这是一个纯模板题,所以没有思路……
由于自己也不是很懂,只是在尝试把它说明白,因此文章下述内容仅供参考,且希望大佬批评指正。球球
关于kmp
字符串匹配最朴素的思想是:一个字一个字匹配,暴搜问题串(A)的每个节点,每个都从头开始校对匹配串(P)。
kmp核心:
优化方法:
如果发生了串不能匹配的情况,尝试探究:这个点之前的n个元素(不包含这个点),是否是P串的前n个元素。如果是,那么就可以直接从这个点开始,匹配P串的第n+1个元素是否可以对应。如果对应,那么就刚好省略了循环n次的时间。
那么,假设在第k个元素匹配失败,说明前k-1个元素都是母串P的匹配成功元素,那么易得除k元素外的前n个元素,一定包含在母串P内。即n ≤ \leq ≤k,因此,我们可以先对母串进行处理,针对母串的每个字符,假设此字符在串的第e个位置(从1数起),设定一个对应的数字E,E的代数意义是:从这个位置向串头数E个数, 这E个元素,恰好与母串串头的前E个元素匹配成功, 且0 ≤ \le ≤ E<e, 并把E存在kmp数组(ne数组)内。
图解:
即在此串的第7个位置上,E=3,向串头方向数3个数(ABC),恰好与母串的串头的前三个元素匹配。那么这样,如果在字符串匹配的过程中,下一位问题串A不是E而是X之类,那么就可以直接跳到母串的第3个位置进行计算。
PS:我习惯于把串的第一位当作0,所以我的E=2。表示从这个位置向串头从E数到0,这E+1个元素,恰好与母串串头的前E+1个元素匹配成功,在跳串的时候跳到第E个位置。
如何制作kmp数组
制作kmp数组是一个”试数“的摸石头过河的过程
显然,第一个元素是肯定没有合条件的子串的,
n
e
0
ne_0
ne0=0, 设定k=
n
e
0
ne_0
ne0=0
对于第
i
(
i
>
1
)
位置的元素,
k
+
1
这个位置是否可以与
i
互相匹配
{
匹配成功:
n
e
i
=
n
e
i
−
1
+
1
匹配失败
:
k
=
n
e
k
重复操作
可以看出,这是一个嵌套的制作
k
m
p
数组的方法。
对于第i(i>1)位置的元素,k+1这个位置是否可以与i互相匹配\begin{cases} 匹配成功:ne_i=ne_{i-1}+1 \\ 匹配失败:k=ne_{k}重复操作 \end{cases} \\可以看出,这是一个嵌套的制作kmp数组的方法。
对于第i(i>1)位置的元素,k+1这个位置是否可以与i互相匹配{匹配成功:nei=nei−1+1匹配失败:k=nek重复操作可以看出,这是一个嵌套的制作kmp数组的方法。
将制作方式写成代码:
ne[0]=0;
for(int i=2, k=0; i<=strlen(b); ++i){
while(j!=0 && b[k+1]!=b[i])k=ne[k];
if(b[k+1]==b[i])k++;
ne[i]=k;
}
匹配:
匹配和制作的方式大致相同。由于并不知道第一个元素是否匹配,因此还是成功匹配的k=0
对于问题串
A
的第
i
个元素,
j
+
1
位置的
P
串是否可以匹配
{
可以匹配
:
j
+
+
不能匹配
:
j
=
n
e
j
重复操作
如果完全匹配
,
j
一定等于母串
P
的串长
对于问题串A的第i个元素,j+1位置的P串是否可以匹配\begin{cases} 可以匹配:j++ \\不能匹配:j=ne_{j}重复操作 \end{cases} \\如果完全匹配,j一定等于母串P的串长
对于问题串A的第i个元素,j+1位置的P串是否可以匹配{可以匹配:j++不能匹配:j=nej重复操作如果完全匹配,j一定等于母串P的串长
for(int i=1, j=0; i<a.size(); ++i){
while(j!=-1 && b[j+1]!=a[i])j=ne[j];
if(b[j+1]==a[i])j++;
if(j==b.size()){
cout<<i-j+1<<endl;
j=ne[j];
}
}
喜闻乐见的x山代码
#include<iostream>
#include<cstring>
using namespace std;
const int N=1100000;
int ne[N];
int main(){
freopen("P3375.txt", "r", stdin);
scanf("%s", a+1);
scanf("%s", b+1);
//处理母串
ne[0]=0;
for(int i=2, k=0; i<=strlen(b); ++i){
while(j!=0 && b[k+1]!=b[i])k=ne[k];
if(b[k+1]==b[i])k++;
ne[i]=k;
}
//匹配父串
for(int i=1, j=0; i<strlen(a); ++i){
while(j!=-1 && b[j+1]!=a[i])j=ne[j];
if(b[j+1]==a[i])j++;
if(j==strlen(b)){
cout<<i-j+1<<endl;
j=ne[j];
}
}
for(int i=0; i<strlen(b); ++i)cout<<ne[i]+1<<' ';
return 0;
}