题目传送门

解题思路:

首先说KMP的作用:对于两个字符串A,B(A.size() > B.size()),求B是否是A的一个字串或B在A里的位置或A里有几个B,说白了就是字符串匹配.

下面创设一个问题背景 : 有两个字符串A,B,求B在A中的位置.

A : abcdcbd

B: cdcb

对于上述问题,我们最先想到也就是最暴力的办法,就是把B整体从1~7每次一格的往右挪,每往挪一格,便把B从头到尾跟A匹配一遍,直到匹配不成功或全部匹配成功,如果匹配不成功,就再整体往右挪一格,这样的时间复杂度最坏可以达到O(nm).

如果字符串再长一点,长度达到1e6,显然暴力是会TLE的,那我们就要考虑优化,上述做法唯一能优化的地方就是往右挪的方式,因为一格一格的挪实在是太笨了,所以我们就要想怎样才能挪的快而且正确呢?

很容易想到的是1.直接移到B的后一位 2.移到失配的位置 ,而这两种做法都是可以被推翻的,见图:

那我们就只能甘于暴力吗?不,所以我们要学习KMP,进入正题:

对于学习KMP,要明白几个重要概念:

对于一个字符串A': abcddc

1.前缀:a,ab,abc,abcd,abcdd.

2.后缀:c,dc,ddc,cddc,bcddc.(前缀和后缀都不包括其本身)

然后我们来看一下KMP的伪代码:

 while(i < a.length() && j < b.length()) {
if(当前位置匹配) i++,j++;
else j = kmp[j]; //跳到一个特定的位置
}
if(j == b.length()) 成功
else 失败

看完后发现其实KMP的核心,就是求kmp[j](大多数人称为next[j]),下面就来说一下kmp[j]怎么求.

kmp[i]表示的是长度为i(1~i)的字符串的最长公共前后缀的长度.(以下的j皆为不包括本次字符求得的最长公共前后缀长度,其实也就是枚举前缀的长度)

例: P: a b c d a b c

kmp[]:0 0 0 0 1 2 3

求当前kmp[i],可以分两种情况:

1.当P[i] == P[j+1]时(见下图),kmp[i] = j+1,因为j=2说明P[1..2]和P[3..4]是完全相等的,而P[5]==P[3],那么P[1..3]和P[3..5]是完全相等的,那kmp[5] = 3;

2.当P[i] != P[j+1]时(见下图),j = kmp[j]往回跳,因为j == 3,说明P[1..3]和P[5..7]完全相等,而后缀的最后加上一个P[i],则意味着我们相当于在P[4]插入一个P[i],要在P[1..3]找一个位置j,使P[i] == P[j+1],转化为第一种情况,求kmp[4],即为kmp[i].那为什么j=kmp[j]呢,举个例子:aba插入一个b,只有两个a相等,后面才有可能成为公共前后缀.还有就是j要跳到什么程度呢?答案就是,当找到P[i]==P[j+1]时,或j跳到0了,说明不可能形成公共前后缀了,就停止.

下面上求kmp[]的代码:

 string l;
j = ;
kmp[] = ;
for(int i = ;i <= n; i++) {
while(j != && l[i] != l[j+]) j = kmp[j];
if(l[i] == l[j+]) j++;
kmp[i] = j;
}

然后,我们再回归问题,如何在A串中匹配B呢?Talk is cheap,show you the code.

 string a,b;
j = ;
kmp[] = ;
for(int i = ;i <= n; i++) {//n为B的长度
while(j != && b[i] != b[j+]) j = kmp[j];
if(b[i] == b[j+]) j++;
kmp[i] = j;
}
j = ;
for(int i = ;i <= m; i++) {//m为A的长度
while(j != && a[i] != b[j+]) j = kmp[j];//匹配失败就往回跳
if(a[i] == b[j+]) j++;//当前匹配成功
if(j == n) {//整个B匹配成功
printf("%d",i - j + );
return ;
}
}

最后说一点,就是对于A和B匹配的时候,并不是i在向后动,而是j在向前动.

最后的吐槽时间:这算法好难理解(自认为),想出这算法的三个人也太nb了,写这篇博客好累,虽然不一定有人看,但心里还是挺高兴的.

AC代码:

//洛谷题的AC代码

 #include<iostream>
#include<cstdio> using namespace std; string l1,l,pp,p2;
int lena,lenb,next[],j; int main() {
l1 = l = " ";
cin >> pp >> p2;
l += pp;l1 += p2;
lena = l.length();
lenb = l1.length();
next[] = ;
for(int i = ;i < lenb; i++) {
while(j != && l1[i] != l1[j+]) j = next[j];
if(l1[j+] == l1[i]) j++;
next[i] = j;
}
j = ;
for(int i = ;i < lena; i++) {
while(j != && l[i] != l1[j+]) j = next[j];
if(l[i] == l1[j+]) j++;
if(j == lenb - ) {
printf("%d\n",i - lenb + );
j = next[j];
}
}
for(int i = ;i < lenb; i++)
printf("%d ",next[i]);
return ;
}

KMP【模板】 && 洛谷 P3375的更多相关文章

  1. KMP字符串匹配 模板 洛谷 P3375

    KMP字符串匹配 模板 洛谷 P3375 题意 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.(如果 ...

  2. 洛谷 P3375 【模板】KMP字符串匹配 || HDU 1686 Oulipo || kmp

    HDU-1686 P3375 kmp介绍: http://www.matrix67.com/blog/archives/115 http://www.cnblogs.com/SYCstudio/p/7 ...

  3. 【数论】卢卡斯定理模板 洛谷P3807

    [数论]卢卡斯定理模板 洛谷P3807 >>>>题目 [题目] https://www.luogu.org/problemnew/show/P3807 [输入格式] 第一行一个 ...

  4. 洛谷 P3375 【模板】KMP字符串匹配

    我这段时间因为字符串太差而被关了起来了(昨晚打cf不会处理字符串现场找大佬模板瞎搞,差点就凉了),所以决定好好补一下字符串的知识QAQ,暂时先学习kmp算法吧~ 题目链接:https://www.lu ...

  5. 洛谷P3375 [模板]KMP字符串匹配

    To 洛谷.3375 KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果 ...

  6. 洛谷 P3375 【模板】KMP字符串匹配 题解

    KMP模板,就不解释了 #include<iostream> #include<cstdio> #include<cstring> #include<algo ...

  7. 【模板】LIS模板 洛谷P1091 [NOIP2004提高组]合唱队形 [2017年4月计划 动态规划11]

    以题写模板. 写了两个:n^2版本与nlogn版本 P1091 合唱队形 题目描述 N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形. 合唱队形是指这样的一种队 ...

  8. 树链剖分模板(洛谷P3384)

    洛谷P3384 #include <bits/stdc++.h> #define DBG(x) cerr << #x << " = " < ...

  9. 洛谷—— P3375 【模板】KMP字符串匹配

    P3375 [模板]KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next. (如 ...

随机推荐

  1. Android抓包分析-fiddler版

    本文介绍的是如何使用Fiddler工具抓取Android应用的HTTP协议的数据包 工具 Genymotion模拟器 笔记本电脑一台(Win7) Fiddler(v4.6.2),下载地址:http:/ ...

  2. 模块学习--random

    1 随机一个0-1之间float >>> random.random() 0.82544262519395 >>> random.random() 0.114854 ...

  3. SpringBoot Controller找不到视图路径

    在启动类加注解@ComponentScan("com.controller")即可,括号里表示Controller所在包名. 参考:https://blog.csdn.net/ji ...

  4. java记录3--抽象

    1.由来 利用抽象类是i为了更好的对类加以分类,例如各种植物有具体名字,也有“植物”这个抽象的词对所有具体植物进行归类. 2.抽象类通常用来作为一个类族的最顶层的父类(表示该类族所有事物的共性), 用 ...

  5. HDU 5587:Array

    Array  Accepts: 118  Submissions: 232  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: 131072/ ...

  6. vmware 因误删Linux 虚拟机磁盘,无法启动处理方法

    有可能我们在做了以下误操作,导致Linux系统无法启动: 1). 磁盘损坏或虚拟机磁盘被我们删除了,而fstab文件没有更新: 2). 由于误操作或其它原因使动态库错误. 1. 首先准备好系统安装盘, ...

  7. Hibernate(九)--N+1问题

    1.在利用Hibernate操作数据库的时候,如果在实体类上设置了表的双向关联.这可能会出现Hibernate N+1的问题. 1.1.一对多: 在一方,查找得到了 n 个对象,那么又需要将 n 个对 ...

  8. Lamda表达式学习笔记二

    Lamda表达式学习笔记二 lamda表达式----方法引用 上一篇讲到Lamda体就是对函数式接口方法的实现 ,在方法体中我们可能会引用其他方法实现逻辑,所以在lamda体中我们可以直接引用器方法 ...

  9. iOS开发的调试技巧

    关于本文: 1.模拟器的快捷键 2.覆盖安装注意事项 3.给模拟器相册增加照片 4.模拟器中程序的数据 5.安装旧版本的模拟器 6.模拟慢网速 7.异常断点与符号断点 1.模拟器的快捷键 常用的模拟器 ...

  10. VS2010如何进行程序调试

    VS2010如何进行程序调试 一.前言 对于初步学习C++的朋友,程序的调试是一项必备的技能.尤其是像C++这样难学的语言,程序调试的基本方法更是至关重要.毕竟,谁也不想自己幸幸苦苦一天敲出来的代码就 ...