呼——終於看懂了KMP——磕了三天了。
題目直達
Q: KMP是幹什麼的?
- 是查詢字串用的,可以查詢到 \(S2\) 字串在 \(S1\) 字串中出現的位置(當然,你可以統計出次數)。
Q: 那複雜度是多少的?
- 通常,我們認為他的複雜度是 \(O(|S1|)\), 雖然有點常數。
常規的的比較方法是直接比較,列舉一個頭,然後逐個比較,複雜度(喜提)\(O(|S1|\times|S2|)\)。慢就在於每次都要從頭掃。很浪費時間 (浪費生命),那實在是太浪費了,有沒有快點的辦法呢?如果一個串,的開頭已經確定了和我是一樣的,那不就可以少用時間了嗎?
我們希望(它/他/她)跳的儘可能遠,但是不能漏掉,這樣就可以節約大把的時間 (生命)。哪麼可以找到 \(S2\) 對於每個 \(i( 1 \le i \le n)\) \(next_i\) 表示 \(S2[0, i - 1]\) 中最大 / 字首和字尾相同 / 的長度,那麼我們就可以在字首匹配失敗後跳轉到字尾匹配。 \(next\) 陣列之和 \(S2\) 相關, 所以可以預處理。
現在問題就變成了如何預處理上了。
假設紅色[0 ~ j]的和橙色的 [i-j-1 ~ i-1] 已經匹配出來了,相等。那麼接下來要匹配 \(j + 1\) 和 \(i + 1\)。假設最好情況 $ S2[j + 1] == S2[i]$, j++就好了。但是如果不等於呢?
那麼我們就要比較[0, j - 1] 和 [i - j] 了, 但是又變回了 \(O(n ^ 2)\)。其實我們可以轉換一下,反正橙色[i-j-1 ~ i-1]的和紅色[0 ~ j]的一致,我們不妨把橙色的字尾移到前面來。
那麼,這時我們就會發現我們要找的 \(j\) 應該變換的值我們之前求過是 \(next[j]\), 直接呼叫,但是為了避免沒法匹配 while 就好了。
上程式碼吧,有註釋的啦
#include <iostream>
using namespace std;
const int kMaxN = 1e6 + 5;
string s1, s2;
int l1, l2, net[kMaxN]; //最大字首和字尾相同的長度, 因為next是關鍵詞,所以用net替代
int main() {
cin >> s1 >> s2;
l1 = s1.size(), s1 = " " + s1; //舒服一點
l2 = s2.size(), s2 = " " + s2;
int j = 0; //此時最長(既匹配到哪一位)
for (int i = 2; i <= l2; i++) { //預處理
while (j && s2[i] != s2[j + 1]) { //j已經為0就不用跳了
j = net[j]; //不行就往回跳
}
if (s2[i] == s2[j + 1]) {//如果比較成功,j++
j++;
}
net[i] = j;//賦值上
}
j = 0;
for (int i = 1; i <= l1; i++) {
while (j && s1[i] != s2[j + 1]) {
j = net[j]; //匹配失敗就往回跳
}
if (s1[i] == s2[j + 1]) { //如果比較成功,j++
j++;
}
if (j == l2) { //成功就輸出
cout << i - l2 + 1 << '\n';
j = net[j];
}
}
for (int i = 1; i <= l2; i++) { //題目最後的要求
cout << net[i] << ' ';
}
return 0;
}