前缀函数与 KMP
前缀函数
-
前缀函数
为 的最大相同真前后缀长度。 -
如无特别说明,本文中所有字符串下标从
开始,长度为 。 -
考虑怎么求前缀函数。
-
首先肯定能想到暴力的
枚举 (子串长度,匹配长度,然后一位位验证)。但太蠢了,找一点性质。 -
结论一:
。- 证明:最多多配一位。如果多配了
位,说明
- 从而
- 即
-
说人话就是,多配的这几位除了
这一位以外,其他几位在 处也可以配上,毕竟他们一样,而且都是 的子串。只有新增的这一位是 时才能配上的(后缀意义下是向右新走了一位)。 -
真前后缀意义下,
,所以一定有 。
- 证明:最多多配一位。如果多配了
-
上面的优化可以抽象化为
。那么,能不能构造出相应的 的转移? -
结论二:考虑在
时将尝试匹配的长度从 减小到 ,使得对于 。运用后缀链接思想, 。-
从而
,仍然失配则求 。可以看出这是一个递归过程,边界条件为 ,此时仍然不行则 。 -
所以
。
-
-
综上所述,令
我们有
-
即前缀函数自动机,有时也被称为 KMP 自动机。
-
我们设势函数
,下面的 指的就是 ,只是为了强调和当前的 强关联。 -
则转移
的摊还代价等于 ,转移 的摊还代价等于 。 -
极限情况下
,即单次回跳仅仅让 。从而转移 的单次复杂度上界为 。注意,负的势函数变化量才是正的时间复杂度。 -
实际上的操作过程中可能先
再 ,但这可以忽略,总次数都是 。 -
显然任意次转移
提供的复杂度和势函数是 以下的,于是因为转移 每次回跳至少令势减小 ,而总势不超过 ,从而回跳次数不超过 。 -
故此算法复杂度为
。该算法可以在线,即一位位地读。给出示范代码。
il void kmp(){
int j=0,n=s.size()-1;
For(i,2,n){
while(j && s[i]!=s[j+1]) j=pi[j];
j+=s[i]==s[j+1];
pi[i]=j;
}
return;
}
KMP 算法
求出现
-
考虑求
中 的所有出现位置。 -
原始的 KMP 算法如下:
-
构造字符串
,这里 是一个分隔符。需要保证其不在 或 中出现。 -
求该字符串的前缀函数。不妨记
,则对于所有 的位置, 中存在一个以 结尾的 。之所以是等于号,是因为总有 。
-
-
该算法其实不太聪明。目前比较常用的做法是,求出
的前缀函数,然后扫一遍 ,维护当前在前缀函数自动机上的位置。复杂度可以通过同样的势能分析做到 ,这里 。 -
考虑推广到特殊一点的情况,譬如势能分析不成立的情况。此时注意到我们的复杂度主要是因为连续失配而炸掉,考虑将失配边路径压缩,即将
边(正边)和 边(反边)结合起来直接构造 ,做到 寻找后继。可以通过倍增实现或者类 AC 自动机实现(事实上,AC 自动机就是多模式串的前缀函数自动机,只是因为多模式串而套了个 trie 的壳子并略改变了 fail 边的逻辑),这里不赘述。
求前缀出现
-
考虑求
中所有前缀的出现次数。 -
我们知道这是 AC 自动机上 DP 的经典问题,但考虑一下能不能不建自动机解决它。
-
可以。首先将
的贡献计算(不考虑 ),然后类似完全背包,利用一下后效性,倒序枚举前缀的长度, 。 -
最后将本来就是前缀的贡献加一下。
-
考虑求
中 所有前缀的出现次数。 -
没有本质区别,只是略去第三步。
求周期
-
考虑求
的最短周期。定义 的周期为 ,使得 。 -
引入 border:若
是 的一个真前缀且 ,则 为 的一个 border。显然 可以有很多不同长的 border,但我们一般只关心最长的。 -
有结论:
的最短周期 。由定义易得,毕竟这相当于直接把 border 平移到了和它匹配的后缀上。
求压缩
-
考虑求
的最短压缩。定义 的压缩为 ,满足 是 的前缀且 可由一个或多个 拼接而来。 -
结论:若
,则最短压缩 ,否则最短压缩 。-
不妨将
划分为若干段,每段长都为 。于是长为 的前后缀应当相等。 -
但这代表着第一块和第二块相等,同理第二块和第三块相等,etc.
-
其最优性显然,若存在更短压缩,则会有更短块,于是
块会更长,故 会更长。 -
若不可整除,则显然即使存在
,也会有 (证明同最优性证明),此时对 分讨即可:-
若
,则 ,显然想要整除只能为 。 -
否则,我不会证。
-
-
在线求本质不同的子串数目
-
考虑求
中本质不同的子串数目,要求支持双端加/删字符。 -
都在线了,肯定是递推啊...不然叫 SA 来不就好了(当然
SA 科技可以硬吃在线,可惜我不会)。 -
只讨论后端加字符。构造
,将其翻转获得 ,对 求前缀函数,则长度不大于 的所有前缀都在 中出现过,故新增子串数为 。 -
其他情况显然同理,复杂度为
。如果是初始+操作的话,初始也许可以考虑用 SA 来卡常。
CF1200E Compress Words
- 题意略。不断求第二个串的
,然后和第一个串的后缀匹配,总复杂度 。
CF808G Anthem of Berland
-
题意略。
-
建出
的前缀函数自动机,设计 dp 如下:-
状态设计:
表示当前考虑完 的前 位,当前匹配了 位,最大已有匹配次数。 -
初始化:
。 -
状态转移:读字符或枚举字符,然后暴力转。
-
-
复杂度
。
P2375 [NOI2014] 动物园
-
题意略。
-
首先显然可以建出前缀函数自动机来解决,但一个更妙的方式是,用求
的方式来求 ,因为对 来说两个结论都成立。于是 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】