洛谷 P3375 【模板】KMP字符串匹配
发布日期:2021-07-01 02:50:13 浏览次数:2 分类:技术文章

本文共 3096 字,大约阅读时间需要 10 分钟。

题目描述

给出两个字符串 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 sborder 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

ABABABCABA

输出 #1

130 0 1

说明/提示

样例 1 1 1 解释
对于 s 2 s_2 s2​ 长度为 3 3 3 的前缀 ABA ,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1 1 1

数据规模与约定

本题采用多测试点捆绑测试,共有 3 3 3 个子任务。

  • Subtask 1(30 points): ∣ s 1 ∣ ≤ 15 |s_1| \leq 15 s115 ∣ s 2 ∣ ≤ 5 |s_2| \leq 5 s25
  • Subtask 2(40 points): ∣ s 1 ∣ ≤ 1 0 4 |s_1| \leq 10^4 s1104 ∣ s 2 ∣ ≤ 1 0 2 |s_2| \leq 10^2 s2102
  • Subtask 3(30 points):无特殊约定。

对于全部的测试点,保证 1 ≤ ∣ s 1 ∣ , ∣ s 2 ∣ ≤ 1 0 6 1 \leq |s_1|,|s_2| \leq 10^6 1s1,s2106 s 1 , s 2 s_1, s_2 s1,s2​ 中均只含大写英文字母。

题意:KMP算法的稍微变形,要输出文本串中所有和模式串匹配的位置,以及 next 数组。


思路:KMP完成一次匹配的时间复杂度是 O(m+n) \text{O(m+n)} O(m+n) ,即使像 HDU 2087 剪花布条 那样不重叠地匹配文本串中的多个模式串,时间复杂度也是 O(m+n) \text{O(m+n)} O(m+n)。但是本题如果不想一下,就可能被卡到 O(mn) \text{O(mn)} O(mn) 的复杂度。

下面的代码按照样例解释进行,在用 nextArr 数组完成一次模式串的匹配后,会像暴力匹配算法一样,将 i 回溯到原来的下个位置,j = 0 。于是,会有两个点 TLE

#include 
using namespace std;const int MAXN = 1e6 + 10;char s[MAXN], p[MAXN];int nextArr[MAXN], slen, plen; namespace kmp1 {
void getNext() {
nextArr[0] = 0, nextArr[1] = 0; for (int i = 1; i < plen; ++i) {
int j = nextArr[i]; while (j && p[i] != p[j]) j = nextArr[j]; nextArr[i + 1] = (p[i] == p[j] ? j + 1 : 0); } } void kmp() {
slen = strlen(s), plen = strlen(p); getNext(); if (slen < plen) return; int j = 0; for (int i = 0; i < slen; ++i) {
while (j && s[i] != p[j]) j = nextArr[j]; if (s[i] == p[j]) ++j; if (j >= plen) {
//如果匹配完成一次 printf("%d\n", i + 1 - j + 1); i = i - j + 1; j = 0; } } }} namespace kmp2 {
void getNext() {
nextArr[0] = -1, nextArr[1] = 0; int pos = 2, cn = 0; while (pos <= plen) {
if (p[pos - 1] == p[cn]) nextArr[pos++] = ++cn; else if (cn > 0) cn = nextArr[cn]; else nextArr[pos++] = cn; } } void kmp() {
slen = strlen(s), plen = strlen(p); getNext(); if (slen < plen) return; int i = 0, j = 0; while (i < slen && j < plen) {
if (s[i] == p[j]) ++i, ++j; else if (nextArr[j] != -1) j = nextArr[j]; //j != 0 else ++i; //j = 0失配 // else if (nextArr[j] == -1) ++i;// else j = nextArr[j]; if (j >= plen) {
//如果匹配完成一次 printf("%d\n", i - j + 1); i = i - j + 1; j = 0; } } }}void printNextArr() {
for (int i = 1; i <= plen; ++i) printf("%d ", nextArr[i]); printf("\n");}int main() {
scanf("%s %s", s, p); kmp1::kmp(); //kmp2::kmp(); printNextArr(); return 0;}

只用改几句就可以过了,我们将模式串的匹配完成视作 j = p[plen] = '\0' ,此时一定会和 i 失配,于是 j = next[j] ,而 i 不进行回溯:

...	if (j >= plen) {
printf("%d\n", i - j + 1); j = nextArr[j]; } ...

提交,可以通过:

转载地址:https://memcpy0.blog.csdn.net/article/details/108239684 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:HDU 2087 剪花布条【KMP】
下一篇:LeetCode C++ 347. Top K Frequent Elements【优先队列】中等

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月18日 06时47分03秒