前言:
额……很久以前就写了KMP模板(只是半知不解),话说看完了manacher,再回过头看KMP,是真TM简单啊!字符串专题整体较抽象,所以必须牢记思路并时常复习
题目描述
如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。
(如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了。)
输入输出格式
输入格式:第一行为一个字符串,即为s1
第二行为一个字符串,即为s2
输出格式:若干行,每行包含一个整数,表示s2在s1中出现的位置
接下来1行,包括length(s2)个整数,表示前缀数组next[i]的值。
输入输出样例
ABABABC
ABA
1
3
0 0 1
说明
时空限制:1000ms,128M
数据规模:
设s1长度为N,s2长度为M
对于30%的数据:N<=15,M<=5
对于70%的数据:N<=10000,M<=100
对于100%的数据:N<=1000000,M<=1000000
样例说明:
所以两个匹配位置为1和3,输出1、3
Solution:
什么是字符串匹配:
简单来讲,就是给定两个字符串,判断长度较短的字符串是否是长字符串的子串,并输出第一次出现的位置。
暴力解法:
用两个下标分别表示当前匹配到的主串和模式串的位置,每次移动主串和模式串的下标并匹配,不匹配时主串回到上个下标的后一个位置,模式串从头开始匹配,不停进行直到模式串下标越界说明匹配成功。这样暴力的复杂度是$O(mn)$,比如(主串:aaaaaaaaab 模式串:ab 暴力匹配显然要在主串上移动下标$n$次且每次模式串移动下标$m$次)
kmp的作用:
我们从暴力解法中,容易看出每次匹配失败后,主串下标的移动和模式串下标的重置使得效率变低。而$KMP$能避免多余的下标移动,使得主串下标每次不回退,模式串直接从上次失配的位置开始匹配,这样使复杂度变成$O(m+n)$。
具体思想:
我们预处理出模式串的从头开始的各子串的最长公共前后缀,当模式串在某一位置失配时,直接将模式串移动到已经匹配的模式串子串的最长公共前后缀的位置并使他们重叠,这样便能防止不必要的移动减损效率。
而预处理$next$数组时,实际上就是一个线性的$dp$,因为我们发现每增加$1$个字符,最长公共前后缀的改变会和前面处理出的$next$有关,举个例子:aba 的最长公共前后缀为$1$,当变为abab时,只需判断新增的b和开始的第$1+1$个字符是否相同。这样就是一个线性递推的过程,但是具体实现时有一些细节,举举例子就能知道了。
讲的不够清楚,网上的博客还行,可以去$B$站看下这个视频
代码:
#include<bits/stdc++.h> #define il inline #define ll long long using namespace std; int next[1000005],m,n; char s1[1000005],s2[1000005]; il void getnext(char *s2){ for(int i=1;i<m;i++){ int j=i; while(j){ j=next[j]; if(s2[j]==s2[i]){next[i+1]=j+1;break;} } } } int main() { scanf("%s%s",s1,s2); n=strlen(s1),m=strlen(s2); getnext(s2); int j=0; for(int i=0;i<n;i++){ while(j&&s1[i]!=s2[j])j=next[j]; j+=s1[i]==s2[j]?1:0; if(j==m)printf("%d ",i-m+2); } for(int i=1;i<=m;i++)printf("%d ",next[i]); return 0; }