Z函式 擴充套件KMP 字首函式的總結

2022-06-18 13:42:13 字數 2892 閱讀 5130

對於乙個字串 \(s\)

我們規定乙個函式 \(z[i]\) 表示 \(s\) 與 \(s[i...n-1]\) 的 lcp(最長公共字首)的長度。

即 \(s[0.....z[i]-1]\) 與 \(s[i...i+z[i]-1]\) 相等

先說構造 \(z\) 函式,再說 \(z\) 函式的應用

首先考慮暴力的構造 時間複雜度 \(o(n^2)\)

char s[n];

inline void getz()\)

但答案需要的是\(|a|\),並且還要將 \(s[0\)~\(|a|\)-\(1]\)倒過來輸出

最後輸出就可以了。

code:

#includeusing namespace std;

const int n=2e5+3;

char s[n];

int len,z[n],siz;

inline void gets()

} return;

}int main() }

maxn=len-maxn;

for(register int i=len+1;i<=siz;++i) cout《對於乙個串 \(s\),如果乙個串既是它的字首又是它的字尾,那麼他就是 \(s\) 的完美子串。用 \(z\) 函式來說,就是 \(i\) 如果滿足 \(i+z[i]==len\) 則 \(i\) 開頭的字尾為完美子串。

1.求完美子串的出現次數:

首先注意到,每乙個完美子串的長度都不相同,這就意味這我們不需要判斷乙個完美子串與另乙個完美子串是否本質相同。

而且大的完美子串中一定包含小的完美子串,這也就啟發我們可以利用 桶+字尾和 的思想來統計出現次數。

那麼如何判斷某乙個子串可以包含某乙個大的完美子串( \(k\) )呢?很顯然,只需要這個點 \(i\) 的 \(z[i]\geq len_k\) 就行了(因為每乙個完美子串也是乙個字首。)

例題:cf126b password

cf432d prefixes and suffixes

//\(z\) 函式蒟蒻會的就這麼點了。。。覺得好的點個贊唄~(贊在文章底部作者欄的右邊)

好吧其實字首函式和 \(kmp\) 的 \(next\) 陣列沒什麼大區別,只不過乙個是下標乙個是長度罷了。

給定乙個長度為 \(len\) 的字串 \(s\) , 其字首函式被定義為乙個長度為 \(n\) 的陣列 \(\pi\)。其中\(\pi[i]\) 的定義為:

1.如果 \(i\) 的字首 \(s[0...i]\) 有一對相等的真字首與真字尾,即 \(s[0.....k-1]=s[i-k+1.....i]\) 那麼 \(\pi[i]\) 就是這個相等的真字首的長度,也就是 \(\pi[i]=k\)

2.如果有不止一對相等的,那麼 \(\pi[i]\) 就是其中最長的那一對的長度;

3.如果沒有相等的,那麼 \(\pi[i]=0\)

簡單來說 \(\pi[i]\) 表示的也就是以 \(i\) 為右端點的字首最長的 \(border\) 長度( \(border\) 的定義看上面)

特別的,我們規定 \(\pi[0]=0\)

如果直接暴力計算字首函式的話:

code:

inline void getpi()

} }return;

}

顯然上面的演算法是 \(o(n^3)\) 的,不夠優秀

考慮優化

這個顯然,如果已經求出了當前的 \(\pi[i]\) 需要求出乙個盡量大的 \(\pi[i+1]\) 時。

\(s[i+1]=s[\pi[i]]\) 的(下標從 \(0\) 開始),此時的 \(\pi[i+1]=pi[i]+1;\)

所以從 \(i\) 到 \(i+1\) 時,字首函式值只可能增加 \(1\), 或者維持不變,或者減少。

此時可以將整個**優化成這樣:

inline void getpi()	

} }return;

}

這個時候,因為起始點變為了 \(\pi[i-1]+1\) 所以只有在最好的情況下才會在這個列舉上限上 \(+1\) ,所以最多的情況時會進行 \(n-1+n-2+2n-3\) 次比較

所以這個時候整個演算法時間複雜度已經是 \(o(n^2)\) 了。但還是不夠優秀

在優化1中,我討論了最優情況下的轉移,那麼這時理所當然的就該來優化\(s[\pi[i]]!=s[i+1]\) 時的匹配了

我們在 \(s[\pi[i]]!=s[i+1]\) 時,根據 \(\pi\) 函式的最優性,我們應該找到第二長的長度 \(j\) 使得 \(s[0....j-1]==s[i-j+1.....i]\) 這樣我們才能繼續用 \(s[i+1]=s[j]\) 時的拓展。

而當我們觀察了一下可以發現:

\(s[0.....\pi[i]-1]=s[i-\pi[i]+1....i]\) 所以第二長 \(j\) ,也就等價於\([0,\pi[i]-1]\) 這個區間中的最長 \(border\) 的長度 ,在一想,這不就是 $\pi[pi[i]-1] $ 嘛?(因為 \(\pi\) 函式,代表的一定是這個區間最長的 \(border\) 的長度)

所以這時我們只需要不停地跳 \(\pi\) 函式,就可以得到當前的 \(\pi[i+1]\) 了。

code:

inline void getpi()\),那麼最長的沒有在 \(s\) 中的字首的長度就為 \(\pi_\)。那麼自然,所有更短的字首也會出現

所以,當新增了乙個新字元後出現的新字串為 \(|s|+1-\pi_\)

所以對於每次加入的字元,我們可以 \(o(n)\) 的算出新出現的子串的數量,所以最終複雜度就為 \(o(n^2)\)

這一段抄的老師的講義。。。(因為我描述不到這麼詳細,我太弱了)

函式的擴充套件

函式的擴充套件有以下三個方面 1 函式的預設值 2 rest引數 3 箭頭函式 函式的預設值 es5傳預設值時,的兩種方法 1 條件判斷 2 三元運算子 bug,如果a 0時,回去後面的值 var sum function a,b es6 let sum a 2,b 3 sum 2,3 rest引數...

函式的擴充套件

函式的擴充套件 1.引數預設值 2.引數的解構賦值 3.rest引數 4 擴充套件運算子 如果不傳值,則預設的值為hello,傳值之後為jieke function foo param foo jieke function foo param hello foo 引數的預設 function foo...

函式的擴充套件

只寫了部分內容,詳細了解 es6入門 函式的name屬性會返回函式名,這個屬性在 es6 之前就已經被絕大多數瀏覽器支援,只是在 es6 完善了這個屬性以及新增了一些新內容而已。function foo console.log foo.name foo letf function console.l...