KMP 強大的next陣列

2022-08-13 23:00:21 字數 3841 閱讀 7375

\(kmp\) 的原理不在這裡仔細講了,主要說說最近刷題總結出的 \(next\) 陣列的強大功能。

部分例題來自《資訊學奧賽一本通》的配套練習。

「基於定義」:我們求的 \(next\) 陣列就是字串到某一位時最長相同前字尾的長度。

注意 \(next\) 陣列求的為「最長」的,那如果想知道乙個字串所有相同的前字尾長度咋辦?

舉個栗子:

假設乙個 \(n\) 位的字串(下標從 \(1\) 到 \(n\)),\(next[n]=p\)

那麼該字串的子串 \([1,p]\) 與 \([n-p+1,n]\) 應是相同的

設 \(next[p]=q\) ,那麼子串 \([1,q]\) 與 \([p-q+1,p]\) 是相同的

綜上,子串 \([1,q]\) 與 \([n-q+1,n]\) 是相同的,即 \(next[next[n]]\) 也是該字串相同前字尾長度

就這樣 \(next\) 一遍遍向前找,直到某一位的 \(next\) 為 \(0\), 拓展出一棵 \(next\) 樹(也叫 \(fail\) 樹)。

例題 \(bzoj3620\)

\(problem:\)

求乙個長度為 \(n\) 的字串所有形似 \(a+b+a\) , 且 \(len(a) \geq k,len(b) \geq 1\) 的子串數目。

\(n \leq 15000\)

\(solution:\)

乙個奇妙的事情是這個題 \(o(n^2)\) 能過。

於是列舉每一位為起點,\(kmp\) 的過程中,\(next\) 值相當於這一段子串 \(len(a)\) 的最大值

如果它小於 \(k\) ,顯然不行。

而若 \(它 \times 2+1 > 子串長度\) 也不行。

所以需要找到合適的 \(len(a)\) 滿足 \(len(a) \geq k\) 且 \(len(a) \times 2 +1 \leq 子串長度\)

這就用到 \(next\) 樹的思想了!

還有乙個小優化,用乙個陣列記錄某一段 \(\geq k\) 的最短的相同前字尾長度,把它作為 \(len(a)\) 判斷比較快。

**:

#include#include#include#includeusing namespace std;

const int n = 15005;

char s[n];

int nxt[n],kk,ok[n],ans;

void kmp(char p)

printf("%d\n",ans);

return 0;

}

「拓展」:這裡的主角為 \(n-next[n]\)

還是舉個栗子:

上圖中 \(n-next[n]=3\) ,那 \(3\) 是什麼呢?

看那些棕圈圈,\(3\) 其實可以叫做字串的 「類」迴圈節,因為字串並不是由這個迴圈節完完整整組成的。

而若乙個字串有真正的迴圈節要滿足什麼條件呢?

答案是 \(n-next[n]\) 整除 \(n\)

同樣舉個栗子就明了了:

對於所有字串, \(n-next[n]\) 只是它最短的迴圈節(類迴圈節),其他迴圈節(類迴圈節)的長度通過 \(next\) 一遍遍向前找求出。

還是 \(next\) 樹的思想,結合栗子即可證明,這裡就不贅述了。

!!!

注意:有真正迴圈節的字串,所有迴圈節長度都為最短迴圈節長度的倍數。而類迴圈節並不滿足這一性質!

例題1 \(bzoj1511\)

\(problem:\)

乙個串是有限個小寫字元的序列,特別的,乙個空序列也可以是乙個串. 乙個串 \(p\) 是串 \(a\) 的字首, 當且僅當存在串 \(b\) , 使得 \(a = pb\). 如果 \(p \neq a\) 並且 \(p\) 不是乙個空串,那麼我們說 \(p\) 是 \(a\) 的乙個 \(proper\) 字首. 定義 \(q\) 是 \(a\) 的週期, 當且僅當 \(q\) 是 \(a\) 的乙個 \(proper\) 字首並且 \(a\) 是 \(qq\) 的字首(不一定要是 \(proper\) 字首). 比如串 \(abab\) 和 \(ababab\) 都是串 \(abababa\) 的週期. 串 \(a\) 的最大週期就是它最長的乙個週期或者是乙個空串(當 \(a\) 沒有週期的時候), 比如說, \(ababab\) 的最大週期是 \(abab\). 串 \(abc\) 的最大週期是空串. 給出乙個串,求出它所有字首的最大週期長度之和.

\(串長度 \leq 10^6\)

\(solution:\)

其實題中說的最大週期就是 \(\neq a\) 的最長「類迴圈節」

\(next\) 樹的思想,用乙個陣列記錄每個「點」在該「樹」上最小的非零祖先,否則會超時

#include#include#includeusing namespace std;

const int n = 1000005;

typedef long long ll;

int n;

int nxt[n],snxt[n];

char s[n];

int main()

printf("%lld\n",ans);

return 0;

}

例題2 \(bzoj4974\)\(problem:\)

乙個串 \(t\) 是 \(s\) 的迴圈節,當且僅當存在正整數 \(k\),使得 \(s\) 是 \(t^k\) (即 \(t\) 重複 \(k\) 次)的字首,比如 \(abcd\) 是 \(abcdabcdab\) 的迴圈節。給定乙個長度為 \(n\) 的僅由小寫字元構成的字串 \(s\), 請對於每個 \(k(1 \leq k \leq n)\),求出 \(s\) 長度為 \(k\) 的字首的最短迴圈節的長度 \(per_i\) 。小 \(q\) 告訴你 \(n\) 以及 \(per_1,per_2,...,per_n\),請找到乙個長度為 \(n\) 的小寫字串 \(s\),使得 \(s\) 能對應上 \(per\) 。

\(n \leq 10^5\)

\(solution:\)

可以發現,\(per_i\) 值其實就是最短「類迴圈節」長度,也就是 \(n-next[i]\)

於是我們可以求出所有 \(next\) 值,然後進行逆向 \(kmp\) ,得出原字串。

**:

#include#include#includeusing namespace std;

const int n = 100005;

int n;

int nxt[n],vis[26];

char s[n];

int main()

for(int j=0;j<26;j++) vis[j]=0;

int k=nxt[i-1];

while(k!=0) vis[s[k+1]-'a']=1,k=nxt[k];

vis[s[k+1]-'a']=1;

for(int j=0;j<26;j++)

if(!vis[j])

}printf("%s",s+1);

return 0;

}

KMP演算法 next陣列

通過上文完全可以對kmp演算法的原理有個清晰的了解,那麼下一步就是程式設計實現了,其中最重要的就是如何根據待匹配的模版字串求出對應每一位的最大相同前字尾的長度。我先給出我的 1 void makenext const char p,int next 214 next q k 15 16 現在我著重講...

KMP演算法 NEXT陣列

kmp和next陣列基本上是一起用的,有了next陣列,才有kmp演算法,講道理來說這兩個都是基於最大前字尾和,也就是說需要用到kmp的時候必須先把next陣列先求出來,next陣列就是由所匹配的word的每個子串的前字尾和最大匹配得到的,說實話next陣列的演算法給優化得已經很無解了,以至於至今我...

KMP演算法的next陣列

本文參考 google 資料結構 c語言 include include author silence time 2012 5 19 description kmp演算法的next using namespace std void next char t,int l,int next else 1 ...