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 长度。

算法内容

这是一个纯模板题,所以没有思路……

由于自己也不是很懂,只是在尝试把它说明白,因此文章下述内容仅供参考,且希望大佬批评指正。球球

关于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=nei1+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;

}
  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值