輕量字串演算法——KMP和Manachar
kmp是用來處理字串匹配的常見簡單演算法,網上可以找到很多講解,這裡就不細講了,一筆帶過。
我們知道,暴力匹配兩個字串的複雜度是 的,很多時候我們都不能接受這個複雜度,考慮如何減小複雜度,我們發現在暴力匹配的過程中,會重複匹配很多地方,所以我們從這裡下手,進行優化。
引入kmp演算法最核心的東西—— $ next$ 陣列:
代表當前字元之前的字串中,有多大長度的相同字首。例如如果 ,代表位置 之前的字串中有最大長度為 $k $ 的相同字首。
意味著在某個字元失配時,告訴你下一步匹配中,模式串應該跳到哪個位置( )。如果 等於 ,則跳到模式串的開頭字元,若 且 ,代表下次匹配跳到 之前的某個字元,而不是跳到開頭,跳過了 個曾經匹配過的字元。
為什麼這 個字元就這樣逃過了?我們可以這樣感性地理解:
若是在 這個位置失配,那麼說明在這個位置之前,模式串和文字串是可以匹配的,也就是一樣的,那麼我們要是可以預處理下次跳到的地方就好了,這個就是我們預處理的結果:$ next$ 陣列。
這樣我們的匹配複雜度就降為 :
假設現在文字串 匹配到 位置,模式串 匹配到 位置
-
如果 ,或者當前字元匹配成功(即 ), , 都加一,繼續匹配下一個字元;
-
如果 ,且當前字元匹配失敗(即 ),則令 不變, 。此舉意味著失配時,模式串P相對於文字串S向右移動了 位。
while(i<slen&&j<plen){
if(j==-1||s[i]==p[j])i++,j++;
else j=Next[j];
if(j==plen)j=Next[j];//到這裡就匹配到了一個模式串了。
}
那麼我們如何快速地求出$ next$ 陣列?
這個過程相當於自己與自己匹配,假設現在對於字串 ,已經處理到了 位置,和自己匹配到了 位置(顯然 ):
-
如果 ,並且 ,那麼就是和自己失配了, ;
-
如果 ,那麼就是是適配了, , 都加一,同時如果下次匹配的時候在 處失配了,那麼我們就跳過列舉前面 個元素,直接匹配 ,所以 。
int j=0;next[0]=-1;
for(int k=1;k<n;k++){
while(j>0&&(p[k]!=p[j])) j=next[j];
j+=(p[k]==p[j]);next[k+1]=j;
}
複雜度 。
單字串匹配的話,就是模板題不說了,kmp最重要的,還是對$ next$ 陣列的運用。(多字串匹配我還是信仰Sam,XD)
很明顯的,我們會得到一個DP方程式:
我們令 表示我們 已經處理到了 ,其中出現了長度為 的連續不吉利數字。
答案顯然就是 ,現在考慮如何轉移。
令 表示,在長度為 的連續不吉利數字後,跟上數字 後,不吉利數字的