KMP自动机

Posted akakw1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP自动机相关的知识,希望对你有一定的参考价值。

KMP自动机

分类:字符串

内容:详细版

前置知识

不会的可以点击链接(如果有)或者前往 OI-Wiki 学习

  • KMP

一些约定

  • 字符集大小默认为m
  • 模板字符串默认为s
  • 文本字符串默认为t
  • |s|指字符串s的长度
  • 字符串下标默认从1开始

简介

KMP自动机主要用于字符串的匹配问题,预处理复杂度为O(|s|*m),可以以严格O(|t|)的复杂度进行字符串匹配(KMP为均摊O(|t|),并且可以处理可持久化字符串匹配问题

同时KMP自动机也是AC自动机(可以处理多个模板串的匹配)的基础。

构造KMP自动机

KMP自动机与KMP的区别在于KMP自动机额外求出了 (trans_{i,j}) 表示在第i位置上往后匹配一个j字符会转移到什么状态(状态在这里指已经成功匹配了多少个字符)。

在下文中,将用fail来代替KMPnextnxt代替上面的trans。

普通地实现KMP自动机

假设我们处理到了第i个状态并且前i-1个状态已经完全处理好了,当前的fail也指向了正确的位置。

考虑每个nxt指向的状态:

nxt[s[i + 1]]显然指向i+1

其余的nxt应当指向一直跳fail后第一个下一个字符能匹配的位置,即:

nxt[i][j] = i;
while(nxt[i][j] && s[nxt[i][j] + 1] != j) nxt[i][j] = fail[nxt[i][j]];
if(s[nxt[i][j] + 1] == j) nxt[i][j] = nxt[i][j] + 1;

但是这样我们没有用到之前求出来的nxt并且复杂度很高,所以我们需要找到一种能用到之前求好了的nxt来快速计算当前nxt的方法。

考虑nxt[fail[i]][j],这个表示的是fail[i]这个状态匹配一个j字符会转移到什么状态,即我们想在第i个状态后接一个j字符,但是s[i + 1]不是这个字符,我们就在fail[i]这个状态后面接着找并且找到了一个状态可以转移。

仔细分析一下这个东西就是我们要求的nxt[i][j]

证明一下:由于nxt[fail[i]][j]要么是从nxt[fail[fail[i]]][j]转移过来的,已经考虑过考虑跳多次fail,要么是直接在fail[i]后面接一个j字符,不需要跳多次fail,所以不用管跳多次fail,那么nxt[fail[i]][j]就是我们要求的nxt[i][j]了。

// 假设字符串长度为 n,字符集大小为 m
// nxt 一开始都是 0
fail[1] = 0;
nxt[0][s[1]] = 1;
for(int i = 1; i < n; i ++) {
    for(int j = 0; j <= m; j ++) {
        if(s[i + 1] == j) nxt[i][j] = i + 1;
        else nxt[i][j] = nxt[fail[i]][j];
    }
}
for(int i = 0; i <= m; i ++)
    nxt[n][i] = nxt[fail[n]][i];

这样写出来又长细节又多,一下没搞好就错了,最重要的是不好背,我们想办法缩成单独一个for循环。

好写又好背的板子

首先我们处理第i个状态时,我们可以先让所有的nxt[i]都是nxt[fail[i]],然后在第i+1个状态再把nxt[i][s[i+1]]设为i+1,这样我们就可以不用把第n个状态单独拿出来

然后我们可以发现在求nxt[i]的时候不需要用到之前的fail所以我们可以不记录所有的fail

for(int i = 1, fail = 0; i <= n; i ++) {
    fail = nxt[fail][s[i]]; // 注意这一行不能和下一行互换
    nxt[i - 1][s[i]] = i;
    for(int j = 0; j < m; j ++)
        nxt[i][j] = nxt[fail][j];
}

字符串匹配

构造完以后匹配就很简单了,直接一直沿着nxt走就行了

以上是关于KMP自动机的主要内容,如果未能解决你的问题,请参考以下文章

KMP自动机

算法零基础KMPTrieAC自动机

《KMP复习 + AC自动机》前传

AC自动机

从KMP到AC自动机

Aho-Corasick automaton