KMP
模板
简单来讲,$KMP$ 算法大概分成两部分
- 求 $nxt$ 数组
- 匹配
首先,$nxt$ 数组的定义为 $pre[i]$ 的最长的一段真前缀,满足这个真前缀同时是 $pre[i]$ 的后缀,或者说是最长的 $border$
匹配的话,大概的思想就是如果我们已经匹配 $i$ 位之后失配,说明之前 $i$ 位是匹配的
那么我们在失配后可以直接取 $pre[i]$ 最长的 $border$ 来进行匹配,也就是 $nxt$ 数组记录的东西
1 | int nxt[maxn]; |
失配树
简单来讲就是 $nxt[i]$ 连 $i$,根是 $0$
几个性质
- $u$ 的子树为 $pre[u]$ 在原串中的所有匹配位置(结束位置
- 从 $u$ 到根的所有节点为 $pre[u]$ 的所有 $border$
扩展KMP
模板
大概就是求两个东西:
- 字符串 $s$ 的每个后缀和 $s$ 的 $lcp$
- 字符串 $t$ 的每个后缀和 $s$ 的 $lcp$
1 | int z[maxn]; |
应用
求字符串 $S$ 的最小周期
$n-nxt[n]$ 即为最小周期
求 $pre(S,i)$ 在 $S$ 中的前缀重复出现次数
$\lfloor \frac{z[i+1]}{i}\rfloor+1$
求 $S$ 的每个前缀串在 $S$ 中的出现次数
例题
简要题意:给定字符串 $S$ 和 $k$,$len(S)=n$,对于所有 $i\in[1,n]$,求 $pre(S,i)$ 是否可以被表示成 $ABAB\cdots A$ 的形式,其中 $A$ 出现 $k+1$ 次,$B$ 出现 $k$ 次,$A$ 和 $B$ 都可以是空串
$n,k\le 10^6$
简要题解:我们注意到 $AB$ 的一定是由 $per(S)$ 组成的,因为 $A$ 是 $AB$ 的前缀,令 $d=\lfloor\frac{len(S)}{per(S)}\rfloor$,则 $AB$ 的长度只能是 $\lfloor\frac{d}{k}\rfloor$
如果 $S\equiv 0(\bmod per(S))$,那么我们只需要满足 $\lfloor\frac{d}{k}\rfloor\ge d\bmod k$,否则必须满足 $\lfloor\frac{d}{k}\rfloor> d\bmod k$,因为 $S$ 分成 $d$ 块之后还剩一个小于 $d$ 的块
求完 $nxt$ 数组后,对于 $S$ 的每个前缀判断即可,时间复杂度 $O(n)$
简要题意:给定 $n$ 个字符串,现在要合并这 $n$ 个字符串,对于第 $i$ 个加入的字符串,我们会删掉这个字符串的最长的一个和前 $i-1$ 个已经合并好的字符串的后缀相等的前缀,求最后合并好的字符串
$n\le 10^5,len\le 10^6$
简要题解:我们发现这个东西有点像是 $border$,我们考虑将两个串都翻转一下,发现正好是求 $border$
注意到这个 $border$ 不能超过第一个串的长度,我们在两个串之间加一个特殊字符即可避免,时间复杂度 $O(len)$
简要题意:给定一个字符串 $S$,求 $S$ 的所有合法的 拆分方案,$S$ 的一个拆分我们定义为 $S=(AB)^kC$,其中 $A,B,C$ 均为非空字符串,且 $A$ 中出现奇数次的字符数量不超过 $C$ 中出现奇数次的字符数量,$k$ 为正整数,两个方案不同当且仅当 $A,B,C$ 至少一个字符不同
$T\le 5,S\le 2^{20}$
简要题解:我们考虑枚举 $AB$ 的长度然后枚举重复出现次数,用 $hash$ 来判断,用树状数组维护 $A$ 的贡献,这样的时间复杂度是 $O(Tn\log n\log 26)$,不足以通过此题
我们发现对于固定的 $AB$,出现次数只有奇数和偶数两种情况,那么现在的瓶颈在于如何快速得到出现次数,我们考虑借助扩展 $KMP$ 的 $z$ 数组,容易得到 $per(S,i)$ 的前缀重复出现次数为 $\lfloor \frac{z[i+1]}{i}\rfloor+1$
时间复杂度 $O(Tn\log 26)$,好像有严格 $O(Tn)$ 的做法
简要题意:给定字符串 $S$ 和 $m$,接下来有 $m$ 次询问,每次求 $S[l\cdots r]$ 的最短循环节长度,这里的循环节的定义为如果 $t$ 是 $S$ 的循环节,则 $S$ 由 $t$ 重复若干次得到
$n\le 5\times 10^5,m\le 10^6$
简要题解:我们考虑每次询问暴力枚举约数 $d$,我们知道当 $n=5\times 10^5$ 时 $max_{i=1}^n\tau(i)=200$,这还算是能够接受的
那么我们如何判断 $S[l\cdots l+d-1]$ 是 $S[l\cdots r]$ 的循环节呢,注意到如果 $S[l\cdots r-d]$ 与 $S[l+d\cdots r]$ 相等的话,$S[l\cdots l+d-1]$ 一定是 $S[l\cdots r]$ 的循环节,这是个很经典的结论
判断相等可以使用字符串 $hash$
简要题意:给定一个字符串 $S$,例如有一个 $aba$ 的印章,我们可以用这个印章完成 $ababa$ 的印刷,中间的 $a$ 被印了两次,但是同一位置上印不同字符是不行,求一个长度最小的印章
$n\le 5\times 10^5$
简要题解:我们考虑先用 $KMP$ 求出 $fail$ 树,容易发现印章上的字符只有可能是 $n$ 到根上的所有点 $u$,且点 $u$ 合法的条件是子树内出现的点的最大间距不超过 $u$
那么我们考虑从根向 $n$ 进行 $dfs$,每次只保留子树内的点,同时维护最大间距,因为只有删点,所以可以使用双向链表做到 $O(1)$ 删点
简要题意:给定一个长度为 $n$ 的模板串 $S$ 和一个空串 $T$,现在有 $m$ 次操作,操作有两种,第一种给定一个字符 $ch$ 和一个整数 $k$,表示在 $T$ 的后面加入 $k$ 个 $ch$;第二种操作给定一个整数 $k$,表示删掉 $T$ 末尾的 $k$ 个字符,问在操作的过程中,是否出现某一个时刻满足 $S$ 是 $T$ 的一个子串
$n,m\le 2\times 10^5,k\le 10^9$
简要题解:我们考虑建出 $S$ 的 $nxt$ 树,我们的第一想法是对于每个点预处理加入 $2^k$ 个字符 $ch$ 后转移到哪个点,但是这样做的时间复杂度和空间复杂度都是 $O(26n\log k)$ 的
我们稍作思考可以发现,每个点只会有一个字符的成功转移,其它字符都会先不断跳 $nxt$ 直到一个可以成功转移的位置,那么我们对于每个点预处理每个字符不断跳 $nxt$ 的位置以及可以成功转移的字符跳 $2^k$ 步后所到达的位置即可
对于删除,我们维护一个栈,记录每次添加字符前所在的位置,每次暴力删除即可,时间复杂度 $O(n\log k)$
简要题意:给定一个长度为 $n$ 的排列 $p_i$ 和一个长度为 $m$ 的序列 $a_i$,对于一个长度为 $n$ 的序列 $a_i$ 和一个长度为 $n$ 的排列 $p_i$,称 $a_i$ 符合 $p_i$ 当且仅当 $a_i$ 互不相同,且将 $a_i$ 从小到大排序会得到 $a_{p_1},\cdots, a_{p_n}$,现在需要对给定的序列 $a_i$ 求其有多少个符合给定排列 $p_i$ 的长度为 $m$ 的子串
$n\le m\le 10^6$
简要题解:首先我们将 $p_i$ 变成 $p’_{p_i}=i$,下面以 $p$ 来代表 $p’$,那么现在的匹配规则就是将 $a_i$ 离散化后与 $p_i$ 是否对应位都相等,注意到这个东西是非常难处理的,因为一个位置的值与其前面的数和后面的数都有关系,我们考虑将每个数的值定义为其前面有多少小于它的数
然后我们考虑求 $nxt$ 数组和 $kmp$ 的过程,我们需要动态维护对于 $a_i$ 在序列 $[nxt_{i-1},i]$ 的值,这个可以用树状数组实现,在 $nxt$ 向会跳的时候我们在树状数组上撤销这些值即可,$nxt$ 指针的移动是均摊 $O(n)$ 的,匹配时用类似的方法即可,时间复杂度 $O(n\log n)$
简要题意:定义 $S$ 的 $k$ 子串为 $S_k=S[k..n-k+1],k\le \lceil\frac{n}{2}\rceil$,求 $S_k$ 的最长 $border$ 长度,$k\in [1,\lceil\frac{n}{2}\rceil]$
$|S|\le 10^6$
简要题解:令 $ans_i$ 表示 $S_i$ 的最长 $border$,容易发现 $ans_i\le ans_{i+1}+2$,画图容易验证,那么我们暴力求 $ans_i$ 即可,判断相等使用 $hash$ 即可,时间复杂度 $O(n)$