前缀函数与 KMP

前缀函数

  • 前缀函数 \(\pi_i\)\(s_{1\sim i}\) 的最大相同真前后缀长度,也即 \(s_{1\sim i}\) 的最长 border。

  • 如无特别说明,本文中所有字符串下标从 \(1\) 开始,长度为 \(n\)

  • 考虑怎么求前缀函数。

  • 首先肯定能想到暴力的 \(O(n^3)\) 枚举 \(i,len,j\) (子串长度,匹配长度,然后一位位验证)。但太蠢了,找一点性质。

  • 结论一:\(\pi_{i+1}\leqslant \pi_i+1\)

    • 证明:最多多配一位。如果多配了 \(k\) 位,说明

    \[s_{\pi_{i+1}-k+1\dots \pi_{i+1}}=s_{i+1-k+1\dots i+1} \]

    • 从而

    \[s_{\pi_{i+1}-k+1\dots \pi_{i+1}-1}=s_{i+1-k+1-1\dots i} \]

    \[s_{\pi_{i+1}-k+1\dots \pi_{i+1}-1}=s_{i-k+1\dots i} \]

    • 说人话就是,多配的这几位除了 \(s_{i+1}=s_{\pi_{i+1}-1}\) 这一位以外,其他几位在 \(i\) 处也可以配上,毕竟他们一样,而且都是 \(s_{1\dots i}\) 的子串。只有新增的这一位是 \(i+1\) 时才能配上的(后缀意义下是向右新走了一位)。

    • 真前后缀意义下,\(\pi_{i+1}<i+1\),所以一定有 \(\pi_{i+1}-1<i\)

  • 上面的优化可以抽象化为 \(\delta(\pi_i,s_{\pi_i+1})=\pi_i+1(s_{i+1}=s_{\pi_i+1})\) 。那么,能不能构造出相应的 \(s_{i+1}\neq s_{\pi_i+1}\) 的转移?

  • 结论二:考虑在 \(s_{i+1}\neq s_{\pi_i+1}\) 时将尝试匹配的长度从 \(\pi_i+1\) 减小到 \(j+1\),使得对于 \(s_{1\dots i},s_{1\dots j}=s_{i-j+1\dots i}\)。运用后缀链接思想,\(j=\pi_i\)

    • 从而 \(\text{if } s_{i+1}=s_{j+1},\pi_{i+1}=j+1\),仍然失配则求 \(j'\)。可以看出这是一个递归过程,边界条件为 \(j=0\),此时仍然不行则 \(\pi_{i+1}=0\)

    • 所以 \(j'=\pi_j(j>0)\)

  • 综上所述,令 \(j=\pi_i\) 我们有

\[\delta(\pi_i,s_{i+1})= \begin{cases} \pi_{i+1}=j+1 & s_{i+1}=s_{j+1} \\ \pi_{i+1}=0 & s_{i+1}\neq s_{j+1}\land j=0 \\ \pi_{i+1}=\delta(\pi_j,s_{i+1}) & s[i+1]\neq s[j+1]\land j>0\end{cases} \]

  • 即前缀函数自动机,有时也被称为 KMP 自动机。

  • 我们设势函数 \(\phi(i)=\pi_i\),下面的 \(\phi(\pi)\) 指的就是 \(\phi\),只是为了强调和当前的 \(\pi\) 强关联。

  • 则转移 \((1)\) 的摊还代价等于 \(O(1)+\phi(1)\),转移 \((2)\) 的摊还代价等于 \(O(1)\)

  • 极限情况下 \(\pi_i=i\),即单次回跳仅仅让 \(\pi-1\)。从而转移 \((3)\) 的单次复杂度上界为 \(O(\pi_i)-\phi(\pi_i)+\phi(\pi_{i+1})\)。注意,负的势函数变化量才是正的时间复杂度。

  • 实际上的操作过程中可能先 \((3)\)\((1)/(2)\),但这可以忽略,总次数都是 \(O(n)\)

  • 显然任意次转移 \((1)\) 提供的复杂度和势函数是 \(n\) 以下的,于是因为转移 \((3)\) 每次回跳至少令势减小 \(1\),而总势不超过 \(n\),从而回跳次数不超过 \(n\)

  • 故此算法复杂度为 \(O(n)\)。该算法可以在线,即一位位地读。给出示范代码。

int pi[maxn]; 
il void skmp(string &s){//需要 s 的下标从 1 开始 
	int len=s.size(),x;
	pi[1]=x=0;
	For(i,2,len){
		while(s[i]!=s[x+1]){
			if(!x) break;
			x=pi[x];
		}
		if(s[i]==s[x+1]) ++x;
		pi[i]=x; 
	}
}

KMP 算法

求出现

  • 考虑求 \(T\)\(P\) 的所有出现位置。

  • 基于构建复合串的做法:

    • 构造字符串 \(P+x+T\),这里 \(x\) 是一个分隔符,需要保证其不在 \(P\)\(T\) 中出现。

    • 求该字符串的前缀函数。不妨记 \(|P|=n\),则对于所有 \(i>n+1\land \pi_i=n\) 的位置,\(T\) 中存在一个以 \(i\) 结尾的 \(P\)。之所以是等于号,是因为总有 \(\pi\leqslant n\)

  • 该算法其实不太聪明。目前比较常用的做法是,求出 \(P\) 的前缀函数,然后扫一遍 \(T\),维护当前在前缀函数自动机上的位置。复杂度可以通过同样的势能分析做到 \(O(n+m)\),这里 \(m=|T|\)

  • 考虑推广到特殊一点的情况,譬如势能分析不成立的情况。此时注意到我们的复杂度主要是因为连续失配而炸掉,考虑将失配边路径压缩,即将 \(\delta\) 边(正边)和 \(\pi\) 边(反边)结合起来直接构造 \(e(\pi,c)\),做到 \(O(1)\) 寻找后继。可以通过倍增实现或者类 AC 自动机实现(事实上,AC 自动机就是多模式串的前缀函数自动机,只是因为多模式串而套了个 trie 的壳子并略改变了 fail 边的逻辑),这里不赘述。

求前缀出现

  • 考虑求 \(S\) 中所有前缀的出现次数。

  • 我们知道这是 AC 自动机上 DP 的经典问题,但考虑一下能不能不建自动机解决它。

  • 可以。首先将 \(\pi\) 的贡献计算(不考虑 \(\pi_\pi\)),然后类似完全背包,利用一下后效性,倒序枚举前缀的长度,\(t_{\pi_i}+=t_i\)

  • 最后将本来就是前缀的贡献加一下。

  • 考虑求 \(T\)\(S\) 所有前缀的出现次数。

  • 没有本质区别。将 \(t_i\) 求法从在 \(S\) 上跑改为在 \(T\) 上跑(\(S\) 的前缀函数自动机)即可,注意要略去第三步。

求周期

  • 考虑求 \(S\) 的最短周期。定义 \(S\) 的周期为 \(p\),使得 \(\forall i\in [1,n-p+1],S_i=S_{i+p}\)

  • \(S\) 的最短周期 \(p=n-\max |border|\)。由定义易得,毕竟这相当于直接把最长 border 平移到了和它匹配的后缀上。

求压缩

  • 考虑求 \(S\) 的最短压缩。定义 \(S\) 的压缩为 \(s\),满足 \(s\)\(S\) 的前缀且 \(S\) 可由一个或多个 \(s\) 拼接而来。

  • 结论:若 \((n-\pi_n)\mid n\),则最短压缩 \(r=n-\pi_n\),否则最短压缩 \(r=n\)

    • 不妨将 \(S\) 划分为若干段,每段长都为 \(r\)。于是长为 \(n-r=n-(n-\pi_n)=\pi_n\) 的前后缀应当相等。

    • 但这代表着第一块和第二块相等,同理第二块和第三块相等,etc.

    • 其最优性显然,若存在更短压缩,则会有更短块,于是 \(r-1\) 块会更长,故 \(\pi_n\) 会更长。

    • 若不可整除,则显然即使存在 \(r\),也会有 \(r>n-\pi_n\)(证明同最优性证明),此时对 \(\pi_n\) 分讨即可:

      • \(\pi_n<=\frac{n}{2}\),则 \(r>n-\pi_n>=\frac{n}{2}\),显然想要整除只能为 \(n\)

      • 否则,我不会证。

  • 其实我们当然能看出求周期和求压缩是同一个问题。

在线求本质不同的子串数目

  • 考虑求 \(S\) 中本质不同的子串数目,要求支持双端加/删字符。

  • 都在线了,肯定是递推啊...不然叫 SA 来不就好了(当然 \(O(n)\) SA 科技可以硬吃在线,可惜我不会)。

  • 只讨论后端加字符。构造 \(Sn=S+c\),将其翻转获得 \(Sn'\),对 \(Sn'\) 求前缀函数,则长度不大于 \(\max \pi\) 的所有前缀都在 \(S\) 中出现过,故新增子串数为 \(|S|+1-\max \pi\)

  • 其他情况显然同理,复杂度为 \(O(n^2)\)如果是初始+操作的话,初始也许可以考虑用 SA 来卡常。

CF1200E Compress Words

  • 题意:Amugae 有一个由 \(n\) 个单词组成的句子。他想把这个句子压缩成一个单词。Amugae 不喜欢重复,因此当他将两个单词合并成一个单词时,会移除第二个单词中与第一个单词后缀相同的最长前缀。例如,他将 "sample" 和 "please" 合并成 "samplease"。Amugae 会从左到右依次合并他的句子(即,先合并前两个单词,然后将结果与第三个单词合并,依此类推)。输出合并过程结束后得到的压缩单词。

  • 不断求第二个串的 \(\pi\),然后和第一个串的(长为 \(\min(|s_1|,|s_2|)\) 的)后缀匹配,总复杂度 \(O(\sum |S|)\)

CF808G Anthem of Berland

  • 题意:给定 \(S,T\),其中 \(S\) 串包含若干问号。试将每个问号变成一个小写字母,使得 \(T\)\(S\) 中出现次数最多。

  • 建出 \(T\) 的前缀函数自动机,设计 dp 如下:

    • 状态设计:\(f_{i,j}\) 表示当前考虑完 \(S\) 的前 \(i\) 位,当前匹配了 \(j\) 位,最大已有匹配次数。

    • 初始化:\(f_{0,0}=0\)

    • 状态转移:读字符或枚举字符,然后暴力转。

  • 复杂度 \(O(26|S||T|)\)

P2375 [NOI2014] 动物园

  • 题意:求 \(num_i\),其定义为 \(s_{1\sim i}\) 的不重叠 border 个数。

  • 首先显然可以建出前缀函数自动机来解决。然而,此时 kmp 的复杂度不再成立,因为我们每次都要从 \(pi_i\) 开始不断回跳直到其对应的 border 不重合,最坏 $O(n^2)。

  • 但注意到此时我们根本不关心最长 border,只关心最长不重合 border!用求 \(\pi\) 的方式来求 \(num\) 即可。最后把这些反边的树建出来,某个点的深度就是其合法 border 个数,根节点(border 为空)深度为 0。于是 \(O(\Sigma |S|)\)

P3435

  • 题意:求 \(S\) 的所有前缀的最大周期之和。

  • 首先我们可以简单求得最大周期。记最小周期为 \(p\),由 kmp,\(p\) 易得,则最大周期 \(p'=\lfloor \frac{n-1}{p} \rfloor \times p\)

  • 错!考虑字符串 aabcaa。显然,其最大周期为 aabca,但 \(p=6-pi_6=2\),于是 \(p'=4\),显然不对。

  • 问题出在哪?发现当 border 本身有周期时,我们可以在所得 \(p'\) 对应的(假最大)周期后面,再跟一个 border 的真最大周期。显然,这个过程是不多层递归的。

  • 另解:魔改 kmp。\(pi\) 是最长 border,而显然,\(n-\min |border|\) 即最大真周期,这里 \(border\) 不为空即可。显然,求解过程可以套 kmp 的结构。

P3426

  • 题意:给定 \(s\),试构造 \(t\),使得 \(s\) 可以由若干个 \(t\) 前后拼接而成,拼接过程中,允许完全相同的字符重叠。

  • 首先不难看出 \(t\) 当为 \(s\) 子串。更进一步地,\(t\) 应该既是 \(s\) 的前缀,又是后缀,否则两端拼不好。

  • 于是意识到 \(t\)\(s\) 的 border。但是,是 \(s\) 的 border 不一定就合法。

  • 考虑拼接时倒数第二个 \(t\) 的结尾位置,假设其为 \(k\),则易见必须有 \(n-k\leq |t|\)。同时,\(t\) 也是 \(s_{1\sim k}\) 的 border。

  • 于是意识到对特定的 \(t\),其如果合法,则 \(k_i+|t|\geq k_{i+1}\)。在 fail 树上从根向 \(n\) 跑,用链表维护 \(1\sim n\),每次下探时,删除兄弟子树内的所有点并更新最大间隔,\(O(n)\),允许我们对 \(n\)\(0\) 的链上每个点对应的 border 检验合法性。

  • 但这只是必要条件,不是充分条件。不过,稍微考察 border 的结构,容易发现如果 \(s_{1\sim x}=s_{l_1\sim r_1}=s_{l_2\sim r_2}\),当 \(r_1+1=l_2\) 时显然可以进行合法扩张,当 \(l_1<l_2\leq r_1<r_2\) 时,由 border 的定义,从 \(l_1,r_1\) 推出 \(s_{x-r_1+l_2\sim x}=s_{l_2\sim r_1}\),从 \(l_2,r_2\) 推出 \(s_{1\sim r_1-l_2+1}=s_{l_2\sim r_1}\),而两个式左恰好构成一个 \(s_{1\sim x}\) 的 border,且恰等于两个后缀的重叠之处。这就给出了一个充分的构造。

  • 很简单,但也很美妙。

posted @ 2023-02-06 11:20  未欣  阅读(109)  评论(0)    收藏  举报