KMP学习笔记

KMP 学习笔记

你说得对,但是还是怀着对于OI的热情来了。

好了,搞一下字符串。

考虑这样的一个问题,给出字符串 SSTT ,在主串 SS 中找到 子串 TT

先定义。

  • 一个 字符串 SS 是将 nn 个字符顺次排列形成的序列,nn 称为 SS 的长度,表示为 S|S|

  • 定义前缀,表示从 [1,i][1,i] 下标顺序组成的字符串,真前缀指字符串除了他本身的所有前缀。后缀就是从 [i,n][i,n] 为下标组成的。真后缀定义类似。

  • 为了方便,定义字符串 SS 中由下标 [i,j][i,j] 组成的字符串记为 S(i,j)S(i,j)

考虑求出这样一个数组 fffif_i 表示 S(1,i)S(1,i) 中,找到一个最长的真前缀 S(1,j)S(1,j),满足该 S(1,j)S(1,j)S(1,i)S(1,i) 的真后缀。fif_i 保存的值就是 jj

他可以被 O(n)O(n) 求出来,先让 f0=1f_0=-1,考虑递推,如果已经知道 [1,i][1,i] 的答案,求 fi+1f_{i+1}

  • 如果 fif_i 保存的答案 jj,满足 sj+1=si+1s_{j+1}=s_{i+1},就有 fi+1=fi+1f_{i+1}=f_i+1

  • 如果不满足,那就一直往后跳 fjf_j,一直到可以满足 sfj+1=si+1s_{f_j'+1}=s_{i+1} 为止。

这样做是没有问题的,原因在于,每次在往前跳的时候(比如从 ii 跳到 fif_i(也就是 jj)),必定可以发现 [j+1,i1][j+1,i-1] 这段一定都是不合法的。因为根据定义,这之间的任何一个位置作为末尾的前缀必然不满足是他的真后缀,(去反证他,如果满足的话,那就和 fif_i 的定义违背了。)

那就达不到求解 fi+1f_{i+1} 的目的。那既然全都不是,一直往前跳也就不会跳过可能的答案了。

而这个字符串的匹配,恰好是 ff 数组的应用。

考虑这样一个问题,给出主串 SS 和子串 TT,要求在 SS 中找到 TT

考虑一个暴力算法,对于主串S,从前往后地,每次到第 ii 位作为起点开始匹配,匹配到对应字符位置不同位置,到 i+1i+1 去匹配。能匹配完就记录答案,这样做是 O(nm)O(nm) 的。

而我们可以做到 O(n+m)O(n+m)

考虑对 TT 求出 ff 数组,每次在匹配的时候,比如到第 ii 位匹配不下去了,我们就对当前位跳 ff 数组,一直到满足 Tj+1=Si+1T_{j+1}=S_{i+1},让 ii 作为终点,让当前的字符串匹配 T(1,j)T(1,j) 的部分。

这么做也是对的,因为,只匹配 T(1,j)T(1,j) 的部分,决定了以 [l+1,r1][l+1,r-1] 为起点去匹配都是不合法的。反证他,如果有比 rr 前的 rr' 可以的话,那必然会有一个更右的 aa' 和他去匹配,也就是 T(1,a)=S(r,i)T(1,a')=S(r',i),又因为 T(1,j)=S(l,i)T(1,j)=S(l,i),两段重合。所以有一个更前的 T(b,j)=S(r,i)T(b',j)=S(r',i)

不难发现,T(1,a)=T(b,j)T(1,a')=T(b',j),那么 aa' 就是一个更大的 fif_i 值了。这和 ff 假设矛盾。

如果匹配完整个串 TT 的话,就相当于在 m+1m+1 的位置失配了。

然后就是实现的细节问题,让指针 jj 一直在停留在 fif_i 的位置,这样方便 fi+1f_{i+1} 去跳 jj 求值,更加简洁一些,具体参照 Link

CF126B

题目大意:在一个字符串里面找到一个最长的前缀,满足他是字符串的后缀,以及他在字符串中间有出现过。

做法就是,在对他跑 KMP 的时候,对每个点的 ff 值打标记,然后遍历 [1,f[n]][1,f[n]],这里面就是满足既是前缀,又是后缀,再看 [1,i][1,i] 为前缀在标记中是否有出现过(如果有的话,说明 SS 有一段前缀的后缀与其相等,那就是在中间出现过了)

CF1029A

差不太多,对 TTff 数组,然后发现其实对 f[n]f[n] 进行保留,复制字符串剩下的,这样就可以达到复制完最小的长度,(因为 f[n]f[n] 是最大的)。

这样一定是优秀的,因为 nf[n]n-f[n] 是最早能够新贡献一个答案的位置了。

其实做的时候都是拼感性的理解。

UVA10298

更厉害了。

毫无规律可循,对第一篇题解补充。

就是他对齐的过程,就是把相等前后缀拉出来“对齐”的过程,更细致一点,就是把字符串复制一遍,找到前后缀相等的过程。

第三张图“依此类推”,其实就是,你把下面那部分的前缀对上去,再对回来一下,就能够发现,他是一一重合的,也就像是 nf[n]n-f[n] 所复制过去的了。

同时,可以发现 f[n]f[n] 是最大的,nf[n]n-f[n] 是最小的,所以不会错过答案。

posted @   June_Failure  阅读(2)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示