\(KMP\) 的原理不在这里仔细讲了,主要说说最近刷题总结出的 \(next\) 数组的强大功能。

部分例题来自《信息学奥赛一本通》的配套练习。


基于定义——字符串相同前后缀

“基于定义”:我们求的 \(next\) 数组就是字符串到某一位时最长相同前后缀的长度。

注意 \(next\) 数组求的为“最长”的,那如果想知道一个字符串所有相同的前后缀长度咋办?

举个栗子:

假设一个 \(n\) 位的字符串(下标从 \(1\) 到 \(n\)),\(next[n]=p\)

那么该字符串的子串 \([1,p]\) 与 \([n-p+1,n]\) 应是相同的

设 \(next[p]=q\) ,那么子串 \([1,q]\) 与 \([p-q+1,p]\) 是相同的

综上,子串 \([1,q]\) 与 \([n-q+1,n]\) 是相同的,即 \(next[next[n]]\) 也是该字符串相同前后缀长度

就这样 \(next\) 一遍遍向前找,直到某一位的 \(next\) 为 \(0\), 拓展出一棵 \(next\) 树(也叫 \(fail\) 树)。

例题 \(bzoj3620\)

\(PROBLEM:\)

求一个长度为 \(n\) 的字符串所有形似 \(A+B+A\) , 且 \(len(A) \geq k,len(B) \geq 1\) 的子串数目。

\(n \leq 15000\)

\(SOLUTION:\)

一个奇妙的事情是这个题 \(O(n^2)\) 能过。

于是枚举每一位为起点,\(KMP\) 的过程中,\(next\) 值相当于这一段子串 \(len(A)\) 的最大值

如果它小于 \(k\) ,显然不行。

而若 \(它 \times 2+1 > 子串长度\) 也不行。

所以需要找到合适的 \(len(A)\) 满足 \(len(A) \geq k\) 且 \(len(A) \times 2 +1 \leq 子串长度\)

这就用到 \(next\) 树的思想了!

还有一个小优化,用一个数组记录某一段 \(\geq k\) 的最短的相同前后缀长度,把它作为 \(len(A)\) 判断比较快。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring> using namespace std; const int N = 15005; char s[N];
int nxt[N],KK,ok[N],ans; void KMP(char p[]){
int len=strlen(p+1),k=0;
nxt[1]=0; ok[0]=ok[1]=-1;
for(int i=2;i<=len;i++){
while(k && p[i]!=p[k+1]) k=nxt[k];
if(p[i]==p[k+1]) k++;
nxt[i]=k; if(k<KK) { ok[i]=-1; continue; }
if(ok[k]==-1) ok[i]=k;
else ok[i]=ok[k];
if(ok[i]*2+1<=i) ans++;
}
} int main()
{
int len;
scanf("%s",s+1);
scanf("%d",&KK);
len=strlen(s+1); for(int i=1;i<=len;i++) {
if(KK*2+1>len-i+1) break;
KMP(s+i-1);
}
printf("%d\n",ans); return 0;
}

拓展功能——字符串循环节

“拓展”:这里的主角为 \(n-next[n]\)

还是举个栗子:

上图中 \(n-next[n]=3\) ,那 \(3\) 是什么呢?

看那些棕圈圈,\(3\) 其实可以叫做字符串的 “类”循环节,因为字符串并不是由这个循环节完完整整组成的。

而若一个字符串有真正的循环节要满足什么条件呢?

答案是 \(n-next[n]\) 整除 \(n\)

同样举个栗子就明了了:

对于所有字符串, \(n-next[n]\) 只是它最短的循环节(类循环节),其他循环节(类循环节)的长度通过 \(next\) 一遍遍向前找求出。

还是 \(next\) 树的思想,结合栗子即可证明,这里就不赘述了。

!!!

注意:有真正循环节的字符串,所有循环节长度都为最短循环节长度的倍数。而类循环节并不满足这一性质!

例题1 \(bzoj1511\)

\(PROBLEM:\)

一个串是有限个小写字符的序列,特别的,一个空序列也可以是一个串. 一个串 \(P\) 是串 \(A\) 的前缀, 当且仅当存在串 \(B\) , 使得 \(A = PB\). 如果 \(P \neq A\) 并且 \(P\) 不是一个空串,那么我们说 \(P\) 是 \(A\) 的一个 \(proper\) 前缀. 定义 \(Q\) 是 \(A\) 的周期, 当且仅当 \(Q\) 是 \(A\) 的一个 \(proper\) 前缀并且 \(A\) 是 \(QQ\) 的前缀(不一定要是 \(proper\) 前缀). 比如串 \(abab\) 和 \(ababab\) 都是串 \(abababa\) 的周期. 串 \(A\) 的最大周期就是它最长的一个周期或者是一个空串(当 \(A\) 没有周期的时候), 比如说, \(ababab\) 的最大周期是 \(abab\). 串 \(abc\) 的最大周期是空串. 给出一个串,求出它所有前缀的最大周期长度之和.

\(串长度 \leq 10^6\)

\(SOLUTION:\)

其实题中说的最大周期就是 \(\neq A\) 的最长“类循环节”

\(next\) 树的思想,用一个数组记录每个“点”在该“树”上最小的非零祖先,否则会超时

#include<cstdio>
#include<iostream>
#include<algorithm> using namespace std; const int N = 1000005;
typedef long long ll; int n;
int nxt[N],snxt[N];
char s[N]; int main()
{
scanf("%d",&n);
scanf("%s",s+1); ll ans=0;
int k=0;
nxt[1]=0; snxt[1]=1;
for(int i=2;i<=n;i++){
while(k && s[i]!=s[k+1]) k=nxt[k];
if(s[i]==s[k+1]) k++;
nxt[i]=k;
snxt[i]=(k?snxt[k]:i);
ans+=i-snxt[i];
}
printf("%lld\n",ans); return 0;
}

例题2 \(bzoj4974\)

\(PROBLEM:\)

一个串 \(T\) 是 \(S\) 的循环节,当且仅当存在正整数 \(k\),使得 \(S\) 是 \(T^k\) (即 \(T\) 重复 \(k\) 次)的前缀,比如 \(abcd\) 是 \(abcdabcdab\) 的循环节。给定一个长度为 \(n\) 的仅由小写字符构成的字符串 \(S\), 请对于每个 \(k(1 \leq k \leq n)\),求出 \(S\) 长度为 \(k\) 的前缀的最短循环节的长度 \(per_i\) 。小 \(Q\) 告诉你 \(n\) 以及 \(per_1,per_2,...,per_n\),请找到一个长度为 \(n\) 的小写字符串 \(S\),使得 \(S\) 能对应上 \(per\) 。

\(n \leq 10^5\)

\(SOLUTION:\)

可以发现,\(per_i\) 值其实就是最短“类循环节”长度,也就是 \(n-next[i]\)

于是我们可以求出所有 \(next\) 值,然后进行逆向 \(KMP\) ,得出原字符串。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm> using namespace std; const int N = 100005; int n;
int nxt[N],vis[26];
char s[N]; int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&nxt[i]),nxt[i]=i-nxt[i]; s[1]='a';
for(int i=2;i<=n;i++){
if(nxt[i]!=0) { s[i]=s[nxt[i]]; continue; }
for(int j=0;j<26;j++) vis[j]=0;
int k=nxt[i-1];
while(k!=0) vis[s[k+1]-'a']=1,k=nxt[k];
vis[s[k+1]-'a']=1;
for(int j=0;j<26;j++)
if(!vis[j]) { s[i]='a'+j; break; }
}
printf("%s",s+1); return 0;
}

KMP——强大的next数组的更多相关文章

  1. 【字符串匹配】KMP算法和next数组的c/c++实现

    KMP算法基本思想有许多博客都写到了,写得也十分形象,不懂得可以参考下面的传送门,我就不解释基本思想了.本文主要给出KMP算法及next数组的计算方法(主要是很多网上的代码本人(相信应该是许多人吧)看 ...

  2. poj 2406:Power Strings(KMP算法,next[]数组的理解)

    Power Strings Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 30069   Accepted: 12553 D ...

  3. KMP(构建next数组)

    字符串匹配算法KMP, 核心思想是尽可能利用已经匹配的结果, 跳过尽可能多的不需要匹配的情况 重点和难点都在next数组的建立上 1. KMP算法的next数组求解 以模式串 a b a c a b ...

  4. KMP算法的next[]数组通俗解释

    原文:https://blog.csdn.net/yearn520/article/details/6729426 我们在一个母字符串中查找一个子字符串有很多方法.KMP是一种最常见的改进算法,它可以 ...

  5. hdu 1358:Period(KMP算法,next[]数组的使用)

    Period Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...

  6. 【LOJ#2507】[CEOI2011]Matching(KMP,树状数组)

    [LOJ#2507][CEOI2011]Matching(KMP,树状数组) 题面 LOJ 题解 发现要做的是排名串的匹配. 然后我们考虑把它转成这个位置之前有多少个数小于当前这个数,这样子只要每个位 ...

  7. Trie树&kmp&AC自动机&后缀数组&Manacher

    Trie 计数+Trie,读清题意很重要 https://vjudge.net/problem/UVALive-5913 kmp AC自动机 模板:https://vjudge.net/problem ...

  8. 深入理解kmp中的next数组

    next数组 1. 如果对于值k,已有p0 p1, ..., pk-1 = pj-k pj-k+1, ..., pj-1,相当于next[j] = k. 此意味着什么呢?究其本质,next[j] = ...

  9. KMP算法的Next数组详解

    转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/32 ...

随机推荐

  1. vue脚手架搭项目 git push超时github网站打不开

    vue: 1.npm install vue-cli -g 全局安装脚手架 2.vue init webpack  name 新建项目 name为项目名称 react: 1..npm install  ...

  2. Canvas文本设置

    本文的应用对Canvas文本设置相关属性进行了汇总,具体使用说明请参考下面代码: <!DOCTYPE html> <html lang="zh"> < ...

  3. Spring Boot 各Starter介绍

    原文链接:https://blog.csdn.net/u014430366/article/details/53648139 Spring-Boot-Starters 最通俗的理解- jar 包,引用 ...

  4. 研发环境容器化实施过程(docker + docker-compose + jenkins)

    目录 背景介绍 改造思路 容器构建 基础准备 中间件容器 外部依赖容器 业务应用容器 容器整合 自动构建容器 Maven相关 非Maven项目 总结 背景介绍 目前公司内部系统(代号GMS)研发团队, ...

  5. Python11_文件的读写

    1.打开和关闭文件(文件对象的方法open,close) file object = open(file_name [, access_mode][, buffering]) 各个参数的细节如下: f ...

  6. flask的url处理器(url_defaults和url_value_preprocessor)

    url处理器的作用:对于一部分资源, 你并不是很清楚该如何设定其 URL 相同的部分.例如可能有一些URL包含了几个字母来指定的多国语言语种,但是你不想在每个函数里都手动识别到底是哪个语言 rom f ...

  7. DOCKER学习_006:Docker存储驱动

    一 镜像的分层特性 在说docker的文件系统之前,我们需要先想清楚一个问题.我们知道docker的启动是依赖于image,docker在启动之前,需要先拉取image,然后启动.多个容器可以使用同一 ...

  8. $Poj3017\ Cut\ The\ Sequence$ 单调队列优化$DP$

    Poj   AcWing Description 给定一个长度为N的序列 A,要求把该序列分成若干段,在满足“每段中所有数的和”不超过M的前提下,让“每段中所有数的最大值”之和最小. N<=10 ...

  9. Spring Security入门(基于SSM环境配置)

    一.前期准备 配置SSM环境 二.不使用数据库进行权限控制 配置好SSM环境以后,配置SpringSecurity环境 添加security依赖   <dependency> <gr ...

  10. 用TensorFlow做图像识别(python)

    一.TensorFlow简介 TensorFlow是由谷歌开发的一套机器学习的工具,使用方法很简单,只需要输入训练数据位置,设定参数和优化方法等,TensorFlow就可以将优化结果显示出来,节省了很 ...