题解:P3375 【模板】KMP

· · 题解

模式串和模板串我分不大清楚,但我们暂且记短串叫做模式串吧。

这个算法大概就是用来判断能否在主串中匹配到子串

KMP 算法在入门阶段是一个很常考的字符串算法,后期高级阶段不知道还考不考,因为我菜。

下面就来讲一讲思想:

一、最长公共前后缀

这里提到了最长公共前后缀的概念。

什么是最长公共前后缀?

前缀:字符串中不包含最后一个字符,必须包含第一个字符的连续子串。

后缀:字符串中不包含第一个字符,必须包含最后一个字符的连续子串。

那么一个字符串的最长公共前后缀,就是这个串中长度最长的一对相同的前缀和后缀。

举个例子:

abcab
ababab
abcba

它们的最长公共前后缀分别是:

ab
abab
a

注意两个点:

  1. 前缀和后缀可以重叠,但是都不可以等于原串。
  2. 不要自己想象着就把后缀倒过来!公共前后缀不是回文串。

二、失配数组

失配数组顾名思义就是在匹配失败的时候用到的数组。

我们记为 fail 数组。

呃,好像有点绕,多读几遍理解一下。 所以 $fail$ 数组存的是最长公共前后缀的长度。 有什么用呢,当模式串和主串匹配失败的时候,我们需要跳到模式串的最长公共前后缀去继续匹配,这就是该数组在匹配失败时的作用。 这里后面会详细讲到,看不懂别着急,理解 $fail$ 的含义就好了。 ## 三、算法流程 在程序的开始,我们遍历主串,同时与模式串进行匹配。 (不得不拿出经典套图了!) ![](https://cdn.luogu.com.cn/upload/image_hosting/h74u59xd.png) $i$ 是遍历主串的变量,$p$ 是我们模式串的指针。 起初,匹配很平稳(两个指针共同向右移动)……直到: ![](https://cdn.luogu.com.cn/upload/image_hosting/d8vq88ke.png) 接下来这一位(画红叉号的位置)不一样! 此时,匹配失败!我们跳转至模式串 $fail_p$ 继续匹配。 ### 为啥要跳 $fail_p$? 因为如果此时我们暴力退回去重新从零开始匹配实在是太慢了。 在我们失配之前,模式串和主串还是可以正常匹配的。仅仅是这一位不匹配了。从模式串的头重新开始岂不是前功尽弃了? 聪明的科学家们想到:既然之前都可以匹配,那之前的子串的**所有公共前后缀一定也可以匹配!** 如下图,三个绿色的串是完全相等的! ![](https://cdn.luogu.com.cn/upload/image_hosting/tj1pbk23.png) 查表可知,$fail_5$ 值为 $2$,那我们跳呗! ![](https://cdn.luogu.com.cn/upload/image_hosting/k85a1cja.png) 好的,跳完我们发现模式串的前两位和主串中$i$ 前面的两位果然是一样的。(都为“ab”) 可以继续进行下一位匹配了。(如果下一位还不匹配,就再调 $fail_2$,一直到跳到可以匹配或者 $p$ 归零) 继续匹配……直到结尾,$p=6$ 模式串跑完了。 ![](https://cdn.luogu.com.cn/upload/image_hosting/rly7z3ry.png) 这时候我们知道:匹配成功! --- 代码放一下。 ```cpp #include<iostream> #include<cstdio> #include<cstring> using namespace std; char s[1000010]; char t[1000010]; int fail[1000010]; int main(){ scanf("%s%s",s+1,t+1); int n=strlen(s+1); int m=strlen(t+1); int p; fail[0]=0; p=0; for(int i=1;i<m;++i){ //这里在求 fail 数组。 while(t[p+1]!=t[i+1]&&p) p=fail[p]; //失配就跳 fail。 if(t[p+1]==t[i+1]){ //下一位可以匹配,我们就前进一位。 p++; } fail[i+1]=p; } p=0; for(int i=1;i<=n;++i){ //这里在进行文本串和模式串的匹配。 while(t[p+1]!=s[i]&&p) p=fail[p]; //失配就跳 fail。 if(t[p+1]==s[i]){ p++; } if(p==m){ //匹配成功! printf("%d\n",i-m+1); p=fail[p]; //这里注意一下,匹配成功以后也要跳 fail,具体原理和失配一样,都是前后缀相同嘛。 //不过如果题目要求匹配的模式串不能重叠,这里就得 p=0 了。好像有个剪布条的题就得这么写。 } } for(int i=1;i<=m;++i){ printf("%d ",fail[i]); } return 0; } ``` 写模板题的题解就是累,还写不明白。欸。 --- ### Update: 2025.4.6:@[Frielen](https://www.luogu.com.cn/user/1125685) 提醒我更正了一个错误的例子,感谢! 2025.7.21:@[jiangjiangQwQ](https://www.luogu.com.cn/user/590609) 提醒我更正了一处错别字,感谢!