【演算法筆記】KMP和AC自動機
KMP
KMP是一種字串匹配演算法,也可以叫它模式匹配演算法。
作用大概是判斷一個字串 \(S \ ,len=n\) 是否是字串 \(T \ ,len=m\) 的字串,並且找出 \(S\) 在 \(T\) 當中每一次出現的位置。
要使用這個演算法必須先知道一個十分重要的思想:\(\text{next}\) 陣列。
\(\text{next}\) 指標
在跑KMP之前需要對於 \(S\) 處理出這個東西(當然,這個東西會出現在很多字串題裡)。
它是一個類似於 AC 自動機的 \(\text{fail}\) 指標的東西(都是在失配時用來轉移)。
但是 \(\text{next}_i\) 是求字首 \(S[1,i]\)
AC 自動機的 \(\text{fail}\) 之後會說。
-
定義:\(\text{next}_{i}=\max\{j\ |\ S[1,j]=S[i-j+1,i] \ , j\le i\}\)
說白了,\(\text{next}_i\) 就是對於 \(S\) 的字首 \(S[1,i]\) ,能夠把這個字首分成兩個完全相等的部分 \(S[1\sim j]\) 和 \(S[i-j+1 \sim i]\) 的最大位置 \(j\)。
如果不存在這樣子的 \(j\) ,那麼 \(\text{next}_i=0\)。
為了方便敘述,定義這個滿足條件 \(S[1,j]=S[i-j+1,i]\)
你可以使用樸素的 \(\text{O}(n^2)\)演算法來計算,但是這很明顯不夠啊,我們還不如不用它,直接暴力匹配 \(S\) 和 \(T\) 得了。
那麼這個時候就有一個引理:
若 \(j\) 是 \(\text{next}_i\) 的一個候選項,那麼滿足 \(j^\prime < j\) 的最大的 \(\text{next}_i\) 的"候選項" \(j^\prime\) 是 \(\text{next}_j\) 。
證明使用反證法+畫圖即可。
也就是說 \((\text{next}_j,j)\) 這個區間當中的數都不是 \(\text{next}_i\)
用它可以對求 \(\text{next}\) 的過程進行優化。
根據它可以發現,對於 \(\text{next}_i\) ,他的所有候選項是 \(\text{next}_i,\text{next}_{\text{next}_i},\text{next}_{\text{next}_{\text{next}_i}}...\)
然後又有一個引理2:
如果 \(j\) 是 \(\text{next}_i\) 的一個“候選項”,那麼 \(j-1\) 一定是 \(\text{next}_{i-1}\) 的”候選項“。
因為如果 \(S[1,j]=S[i+j-1,i]\) ,那麼肯定會有 \(S[1,j-1]=S[i+(j-1)-1,i]\) 。
(畫圖即可證明)
那麼我們在計算完 \(\text{next}_{i-1}\) 之後,我們讓 \(\text{next}_i\) 的所有候選項變成 \(\text{next}_{i-1}-1,\text{next}_{\text{next}_{i-1}}-1,\text{next}_{\text{next}_{\text{next}_{i-1}}}-1...\) 就可以了。
複雜度就變成了線性的。
實現
在有了 \(\text{next}\) 之後,我們完成了對 \(S\) 的自行匹配。
現在還需要做一個類似的過程,讓 \(S\) 和 \(T\) 進行匹配。
我們定義 \(f_i=\max\{j \ | \ T[i-j+1,i]=S[1,j] \ ,j \le i\}\)。
如果 \(f_i=n\) 那麼就證明,\(S\) 在 \(T\) 中出現了一次,並且它出現在區間 \(T[i-n+1,i]\)。
我們類比上面 \(\text{next}\) 的求法就能求出 \(f\)。
\(\text{next}\) 的求法說通俗點就是:
假設 \(\text{next}_{i-1}\) 已經求出來,那麼嘗試一直擴充套件這個 \(j\) ,
如果說下一個位置 \(j+1\) 無法滿足 \(S[j+1]=S[i]\) ,那麼令 \(j=\text{next}_j\) (失配),直到 \(j=0\) 再停止。
如果說下一個位置是滿足要求的,那麼令 \(j=j+1\)。
複雜度是 \(\text{O}(n+m)\)。
#include<bits/stdc++.h>
using namespace std;
const int si=1e6+10;
int n,m;
string s,t;
int Next[si],f[si];
int main(){
Next[1]=0;
cin>>s>>t;
n=(int)s.size(),m=(int)t.size();
s=' '+s,t=' '+t;
for(register int i=2,j=0;i<=n;++i){
while(j>0 && s[i]!=s[j+1]) j=Next[j];
if(s[i]==s[j+1]) ++j;
Next[i]=j;
}// calc next
for(register int i=1,j=0;i<=m;++i){
while(j>0 && (j==n || t[i]!=s[j+1])) j=Next[j]; // 這裡把 j==n 放到裡面了,有些要你求 Border 的題要提出來寫(比如lg3375)
if(t[i]==s[j+1]) ++j;
f[i]=j;
if(f[i]==n){
// S exist in T.
}
}// calc f
}
AC自動機
咕咕咕
Reference
《演算法競賽進階指南》
本文來自部落格園,作者:black_trees,轉載請註明原文連結:https://www.cnblogs.com/BTeqwq/p/kmp-acautomaton.html