# 【模板】KMP
KMP 用在字串匹配,可以在字串 $S$ 中找到哪些地方出現過字串 $P$。
核心思想是優化樸素的字串匹配過程。匹配可以視作,每個位移過後的 $P$,有哪些與對應的 $S$ 子字串相等:
```
S AABAABAAC
P1 AABAAc
P2 Aabaac
P3 aabaac
P4 AABAAC
每個都匹配到失敗為止,並且從匹配失敗之後用小寫表示
```
- **在匹配過程中,想辦法跳過會更早失敗的 $P$。**
在匹配過程中,有時會失敗在某個地方,如在第 1 次匹配中 $S[4] \ne P[4]$。
第 2和第 3 次匹配,會在更早的地方失敗。我們想直接跳過 2、3 次匹配。
要找到下一個至少不會在 $i<4$ 就匹配失敗的。
可以發現,這相當於把 $P$ 字串位移直到其次長前綴 = 匹配失敗前的後綴(最長就根本沒位移):
```
P1 AAB[AA]c
P4 [AA]BAAC
```
- **訂定函數 $f(i)$,代表 $P[0,i)$ 次長共同前後綴長度(最長但不包括 $P[0,i)$ 本身)**
上面的想法,相當於以 $i$ $j$ 兩個指針來匹配 $S$ 和 $P$,一但失敗就使 $j$ 回到最次長前綴。
定義 $f(i)$ 可以方便地做到這件事。
- 轉移:$f(i+1) = f(j)+1,\ P[j] = P[i]$
只要 $P[f(i)] = P[i]$,則 $f(i+1) = f(i)+1$
那如果不相等呢?也還有機會。
因為 $P[0, f(i)) = P[i-f(i),i)$,所以 $P[0,f(f(i))) = P[i-f(f(i),i)$。
此時只要從 $f(i)$ 退到 $f(f(i))$,再試試看就好了。
- 基底:$f(0) = -1,\ f(1) = 0$
顯然,$f(1) = 0$。那 $f(0)$ 呢?
在匹配過程中會出現 $f(0)$,即是當 $S[i]$ 和 $P[0]$ 匹配,而且還失敗的時候。
這時就直接放棄這個 $S[i]$,從 $S[i+1]$ 和 $P[0]$ 開始重新匹配了。
我們令 $f(0) = -1$,方便我們知道 $S[i]$ 和 $P[0]$ 匹配失敗,該直接跳過了。實作時會體現 $-1$ 的方便性。
以下是 $f$ 建表的實作:
```cpp
f[0]=-1, f[1]=0;
for (int i=1,j; i<p.size(); i++)
{
for (j=f[i]; j>=0 && p[j]!=p[i]; j=f[j]);
f[i+1] = j+1;
}
```
- 以 $i$ 遍歷 $S$,$j$ 遍歷 $P$,對於每個 $i$ 找到第一個與其匹配的 $j$
匹配 $i$ 與 $j$ 一但成功,就能幫下個 $i$ 找匹配的 $j$。
否則,就找到下一個可能與 $i$ 匹配的 $P$。
另外,當 $i$ 與 $j=0$ 匹配失敗,就 $i++,j=0$。
出現完全成功配對的條件,是過程中發生,$i=|S|-1$ 和 $j=|P|-1$ 匹配成功。
下層迴圈中 $j = |P|$,必須使 $j = f[j]$。實作用到了一個細節是,`p[p.size()]` 會拿到 `p.end()`,所以不用擔心 RE。
```cpp
for (int i=0,j=0; i<s.size(); i++,j++)
{
while (j>=0 && s[i]!=p[j]) j = f[j];
if (j == p.size()-1) ans++;
}
```