(之前學的一些東西都沒打筆記,給忘的差不多了。從這個開始要記得寫筆記了。)
注意事項:所有的字串的下標從1開始。
KMP
對於一個字串 s ,定義它的字首陣列a,其中a[i]表示子串s[1...i]字首與字尾相同的最大長度(不包括串自身)。
對於樸素的演算法,自然是n^2的暴力。考慮利用前面位置的值來計算當前位置的值。
設當前位置為i。發現a[i]<=a[i-1]+1,同時發現當s[i]=s[a[i-1]+1]時,a[i]=a[i-1]+1。
那麼如果不相等呢?
我們需要找到一個更小的j,考慮j的取值可能在哪裡。我們需要找到一段滿足s[i-j+1...i]=s[1...j]的,才能快速得出當前答案。
發現a[a[i-1]]的值滿足這一性質(參考下圖)。所以我們可以不斷遞迴,直至j=0。(要注意,j=0時有可能a[i]=1。)
用途:
我們處理出來這個陣列有什麼用呢?
首先當然是匹配。假設當前有一個串S,一個模式P,求P在S的哪些位置能匹配?
我們將兩個字串連線起來:P+‘#’+S(‘#’表示一個在兩個串中都不出現的字元)。然後計算出 |P|+2~|P|+|S|+1 的字首陣列。根據字首陣列的性質,當 a[i]=|P| 時,P在S中出現了一個匹配。
計算一個串中,本質不同的串的數量。
考慮每次加入一個字元,計算產生了多少個不同的串。從末尾加入一個字元,然後設當前串的反串為S'。處理出每個位置的字首陣列。整個序列的字首陣列的最大值即為增加的本質相同的串。
計算每個字首的出現次數。
容易,計算有多少個字首陣列的值是相同的就行了。
exKMP
exKMP,也叫 Z 函式。定義字串S的Z函式為:z[i]表示字串S與以i開頭的字尾的LCP。
考慮字串中常用的處理方法:利用前面已經求得的資料計算當前的資料。
我們維護一個l、r,表示與S的某個字首相同的、右端點最右的區間,其實就是S[i...i+z[i]]。這裡我們欽定z[1]=0,從第2個數開始計算。
設當前的位置為i。當i<r時,由於S[i...r]=S[i-l+1...r-l+1],所以考慮透過z[i-l+1]求z[i]。若z[i-l+1]<r-i+1,則z[i]=z[i-l+1]。因為如果z[i]>z[i-l+1],那麼根據l與r的定義,S[z[i-l+1]+1]=S[i-l+1+1],即z[i-l+1]會更大。若z[i-l+1]≥r-i+1,那麼我們將z[i]暫時設為r-i+1,然後暴力延伸。
當i≥r時,將z[i]設為0,然後暴力延伸。記得計算完一個z[i]後,要更新l、r。
考慮時間複雜度的正確性。每一次延伸,如果沒有暴力延伸,則時間複雜度為O(1);如果暴力延伸,則r一定會改變,又由於r最大為n,所以最多擴充套件|S|位。總的複雜度為O(|S|)。
應用:
同KMP一樣,可以匹配,可以計算本質不同的子串數量。
計算一個串的最小整週期,即對於一個串S,求一個長度最小的串t,使得t可以透過若干次複製構成S。算出位置i的z[i]後,若i-1為n的因數且z[i]+i-1=|S|,則S[1...i-1]為S的整週期。