KMP
KMP算法
前提介绍
KMP算法是一种看模式串在主串中出现次数的优化算法 复杂度为O(n+m)
这个算法理解了不难,不理解只背模板早晚会挂,而且容易忘,建议从头到尾一步一步看完
为了方便描述,我们把S作为子串,T作为模式串,然后下标一定要从1开
设len1为长串,len2为模式串(即短串)
本讲解是参考李煜东蓝书的,把部分不易懂的写了出来,希望对大家有帮助
next数组求解
短串: A B A B A A
下标: 1 2 3 4 5 6
数组: 0 0 1 2 3 1
数组指nxt数组
nxt[2]代表s[1]~s[2]即”AB”,前缀为”A”,后缀为“B”,共有元素的长度为0.
nxt[3]代表s[1]~s[3]即”ABA”,前缀为”AB”,后缀为”BA”,最大前后缀即”A”,长度为1.
nxt[4]代表s[1]-s[4]即”ABAB”,前缀为”ABA”后缀为“BAB”,最大前后缀即”AB”,长度为2.
nxt[5]代表s[1]~s[5]即”ABABA”,前缀为”ABAB”,后缀为”BABA”,最大前后缀即”ABA”,长度为3.
nxt[i] 表示从第1位到第i位 除去前后都是自身的最大前后缀(所以上面的例子不能有nxt[6])
我们先好好理解一下nxt数组的意思,然后大声喊十遍:nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,nxt是最大前后缀,
相信大家都已经喊完了,或者跳过了,然后我们看如何求解nxt数组:
朴素做法
对于 $A[1 - i]$依次枚举 $j∈[1,i-1]$, 并检查 $A[i-j+1,i]$ 和 $A[1,j]$,复杂度$O((len2)^3)$
然后我尝试着写了份代码(最好还是看一看挺好懂的):
1 | for(int i=1;i<=len2;i++){ |
KMP优化
在讲KMP思想之前
我们先证明几个有关KMP的引理
引理
我们先引入一个概念:候选项
如果存在一个j使得$A[1,j]$和$A[i-j+1,i]$相等,那么我们就能认为$j$是$nxt[i]$的一个候选项,很显然,$1 \le j \le nxt[i]$
现在我们需要证明:若$j_0$是$nxt[i]$的一个候选项,则小于$j_0$的最大的$nxt[i]$的候选项是$nxt[j_0]$。换言之,$[nxt[j_0]+1,j_0-1]$之间一定不含$nxt[i]$的候选项
这个玩意很重要,看出来这个你的kmp就学完了
这个要用反证法:
假设在$[nxt[j_0]+1,j_0-1]$之间存在一个$j_1$可以当作候选项
我们可以发现,若$j_1$是候选项我们可以列出下面两个式子
$A[1,j_1]$ = $A[i-j_1+1,i]$
$A[1,j_0]$ = $A[i-j_0+1,i]$
然后由于是前后缀,所以说第二个式子的每边的后k个也是相等的
$A[j_0-k+1,j_0]$ = $A[i-k+1,i]$
我们钦定$k=j_1$
所以说
$A[j_0-j_1+1,j_0]$ = $A[i-j_1+1,i]$
发现和第一个式子有重合,直接把第二个式子换成
$A[1,j_1]$ = $A[j_0-j_1+1,j_0]$
与$nxt[j_0]$的最大的性质不符,故假设不成立,即引理正确
如果看不懂,不用看上面的一堆式子直接看图就明白了!
设最浅的块是a,其次是b,最深的是c
则:$c$为$nxt[j_0]$
$b+c$ 为 $j_1$
$a+b+c$ 是 $j_0$
KMP的操作及实现
由刚才我们发现的这一性质,我们可以发现KMP的若干性质
1.我们可以发现当$nxt[i-1]$确认时,我们就可以知道$nxt[i]$的所有border从大到小是$nxt[nxt[i]]$,$nxt[nxt[nxt[i]]]$……
2.我们还可以发现若$j$是$nxt[i]$的border,那么$j-1$也是$nxt[i-1]$的border
证明
这个证明很显然,$j$如果说是$nxt[i]$的border,则$A[i-j+1,i]$和$A[1,j]$相等
所以说$A[i-j+1,i-1]$和$A[1,j-1]$必然相等
所以我们在计算$nxt[i]$时,只需要把$nxt[i-1]+1$ , $nxt[nxt[i-1]]+1$ , $nxt[nxt[nxt[i-1]]]+1$ ……
作为border即可,且它们一定是从大到小排列
最后,我们就可以告诉大家怎么求了
首先,我们把$nxt[1]$赋为0
假设我们已经处理到第$i$位($2\leq i\leq len$)
那么此时$nxt[1]$到$nxt[i]$的值都是已知的
我们假设当前的候选项是$j$
我们要处理$i+1$时,若$j+1$和$i+1$不可以匹配就让$j$跳到$nxt[i]$,若可以匹配或者现在border已经变成0了,就要把$nxt[i+1]$搞成$j$
放个代码
1 | const int N=1e6+10; |
然后有人要问了,那你求$nxt$有什么意义吗,我们要求的不是$nxt$啊
那么我们此时再假设一个$f$数组表示$f[i]=max${$j$},$j$表示S的前j位与T的$T[i-j+1,i]$位相等
我们发现是不是$f$数组的定义其实和$T$数组几乎一样啊
所以我们可以直接用求$nxt$数组的思想,写出求长串的思想:
1 | void get_f(){ |