1. 程式人生 > >輕量字串演算法——KMP和Manachar

輕量字串演算法——KMP和Manachar

  • KMP

         \ \ \ \ \ \ \, kmp是用來處理字串匹配的常見簡單演算法,網上可以找到很多講解,這裡就不細講了,一筆帶過。

      

  \ \ \ \ \ \ \, 我們知道,暴力匹配兩個字串的複雜度是 O ( n 2 )
O(n^2)
的,很多時候我們都不能接受這個複雜度,考慮如何減小複雜度,我們發現在暴力匹配的過程中,會重複匹配很多地方,所以我們從這裡下手,進行優化。

         \ \ \ \ \ \ \,

    引入kmp演算法最核心的東西—— $ next$ 陣列:

         \ \ \ \ \ \ \, 代表當前字元之前的字串中,有多大長度的相同字首。例如如果 n e x t [ j ] = k next [j] = k ,代表位置 j j 之前的字串中有最大長度為 $k $ 的相同字首。

         \ \ \ \ \ \ \, 意味著在某個字元失配時,告訴你下一步匹配中,模式串應該跳到哪個位置( n e x t [ j ] next [j] )。如果 n e x t [ j ] next [j] 等於 1 -1 ,則跳到模式串的開頭字元,若 n e x t [ j ] = k next [j] = k k > 0 k > 0 ,代表下次匹配跳到 j j 之前的某個字元,而不是跳到開頭,跳過了 k k 個曾經匹配過的字元。

         \ \ \ \ \ \ \, 為什麼這 k k 個字元就這樣逃過了?我們可以這樣感性地理解:

         \ \ \ \ \ \ \, 若是在 j j 這個位置失配,那麼說明在這個位置之前,模式串和文字串是可以匹配的,也就是一樣的,那麼我們要是可以預處理下次跳到的地方就好了,這個就是我們預處理的結果:$ next$ 陣列。

         \ \ \ \ \ \ \, 這樣我們的匹配複雜度就降為 O ( n ) O(n)

         \ \ \ \ \ \ \, 假設現在文字串 S S 匹配到 i i 位置,模式串 P P 匹配到 j j 位置

  • 如果 j = 1 j = -1 ,或者當前字元匹配成功(即 S [ i ] = P [ j ] S[i] = P[j] ), i i j j 都加一,繼續匹配下一個字元;

  • 如果 j 1 j \neq -1 ,且當前字元匹配失敗(即 S [ i ] P [ j ] S[i] \neq P[j] ),則令 i i 不變, j = n e x t [ j ] j = next[j] 。此舉意味著失配時,模式串P相對於文字串S向右移動了 j n e x t [ j ] j - next [j] 位。

while(i<slen&&j<plen){
  if(j==-1||s[i]==p[j])i++,j++;
  else j=Next[j];
  if(j==plen)j=Next[j];//到這裡就匹配到了一個模式串了。
}

       &ThinSpace; \ \ \ \ \ \ \, 那麼我們如何快速地求出$ next$ 陣列?

       &ThinSpace; \ \ \ \ \ \ \, 這個過程相當於自己與自己匹配,假設現在對於字串 p p ,已經處理到了 k k 位置,和自己匹配到了 j j 位置(顯然 k &gt; j k&gt;j ):

  • 如果 j &gt; 0 j&gt;0 ,並且 p [ k ] p [ j ] p[k]\neq p[j] ,那麼就是和自己失配了, j = n e x t [ j ] j = next[j]

  • 如果 p [ k ] = p [ j ] p[k]= p[j] ,那麼就是是適配了, k k j j 都加一,同時如果下次匹配的時候在 k + 1 k+1 處失配了,那麼我們就跳過列舉前面 k k 個元素,直接匹配 j + 1 j+1 ,所以 n e x t [ k + 1 ] = j + 1 next[k+1]=j+1

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;
}

       &ThinSpace; \ \ \ \ \ \ \, 複雜度 O ( n ) O(n)

       &ThinSpace; \ \ \ \ \ \ \, 單字串匹配的話,就是模板題不說了,kmp最重要的,還是對$ next$ 陣列的運用。(多字串匹配我還是信仰Sam,XD)

       &ThinSpace; \ \ \ \ \ \ \, 很明顯的,我們會得到一個DP方程式:

       &ThinSpace; \ \ \ \ \ \ \, 我們令 f ( i , j ) f(i,j) 表示我們 X X 已經處理到了 i i ,其中出現了長度為 j j 的連續不吉利數字。

       &ThinSpace; \ \ \ \ \ \ \, 答案顯然就是 i = 0 m 1 f ( n , i ) \sum_{i=0}^{m-1}f(n,i) ,現在考慮如何轉移。

       &ThinSpace; \ \ \ \ \ \ \, g ( i , j ) g(i,j) 表示,在長度為 j j 的連續不吉利數字後,跟上數字 j j 後,不吉利數字的