失配指针
定义 fail 为广义的 \(\pi\) 函数,即对于 \(n\) 个字符串,\(fail_{i,j}=\max\limits_{k=1}^{n} \pi(S_{k}\#S_{i,1\sim j})\)。
上面那个是一种定义,但事实上,在 \(\rm AC\) 自动机中,fail 是具有指向性的,它指向的是所有模式串中,前缀与当前字符串某个前缀的后缀匹配可以产生最大匹配的那个串,它与当前串匹配的最后一个结点。
构建 AC 自动机
首先对所有模式串建立 Trie 树,显然 Trie 树上的每一个结点都对应某个模式串的某个前缀。
然后将每个 fail 都指向匹配的结点,显而易见,所有的 fail 都指向一个祖先节点,这个性质后面要用到。
最后构建转移边 Auto,Auto 指向跳 fail 过程中存在字符 \(c\) 的结点。
具体来说,可以以 \(\rm BFS\) 序遍历 Trie 树,在遍历的同时更新答案,转移式如下:
\[ \begin{cases} fail_u = Auto_{fail_{fa_u}, c} \\ Auto_{u, c} = Auto_{fail_u, c} \end{cases} \]建 \(\rm AC\) 自动机复杂度为 \(\Theta(|\Sigma|n)\)。
文本串匹配
遍历文本串,直接按照转移边走即可,如果要统计每个模式串出现的次数,就将所有经过的点权值 \(+1\),再在 fail 树上差分即可计算。
令模式串为 \(S\),文本串为 \(T\),总时间复杂度为 \(\Theta(|\Sigma||S|+|T|)\)。
首先有一个显然的 \(\Theta(|s|\sum|t|)\) 暴力,枚举文本串的转移点,显然其不能超过最大串长,转移式为:
\[ f_i=\bigvee_{j=i-|s|}^{i-1}\{f_j\wedge[t_{j+1\sim i}\in s]\} \]这个式子的核心在于,\(f_{i}=true\) 当且仅当它存在一个后缀,这个后缀是某个模式串,并且删去这个后缀以后的文本串仍合法。
于是考虑建立 \(\rm AC\) 自动机,将文本串放在自动机上跑的时候记 \(f_{i}\) 表示当前前缀是否合法。
存在如下 \(\rm AC\) 自动机性质:如果 \(v\) 可以从 \(u\) 开始由跳 fail 得到,即 fail 树上 \(v\) 是 \(u\) 的祖先,那么 \(v\) 代表的字符串是 \(u\) 代表的字符串的一个后缀。
所以从 \(u\) 开始遍历跳 fail 经过的结点,如果它代表的字符串长度为 \(l\),且为某个模式串的结尾,则将 \(g_{u,l}\) 记为 true。 由于 \(|s|\leqslant 10\),所以只有当 \(l\leqslant 10\) 的时候 \(g_{u,l}\) 可能为 true,故直接状压成 \(g_{u}\),其二进制下第 \(l\) 位表示之前 \(g_{u,l}\) 的值。
最后直接将文本串放在 \(\rm AC\) 自动机上跑即可,记 \(now_{i}\) 表示文本串中当前点前 \(i\) 位的 \(f\) 值为多少,易知 \(i\leqslant 10\),故也可以状压成一个数 \(now\)。 易知 \(f_{i}=true\) 当且仅当 \([now \operatorname{or} g_{u}]=true\)。
总时间复杂度 \(\Theta(|\Sigma|n|s|^{2}+m|t|)\)。
