擴充套件 KMP 學習筆記
前置芝士
KMP
洛谷模板 & \(\texttt{My solution}\)
問題概括
擴充套件 KMP:求出字串 \(S\) 的所有後綴與 \(T\) 的最長公共字首長度,時間複雜度 \(\mathcal{O}(|S|+|T|)\)。
該解法思想與 KMP 類似,所以稱作擴充套件 KMP。
解法
\(next\) 陣列
定義 \(next_i\) 為 \(T\) 由 \(i\) 開始的字尾與 \(T\) 的最長公共字首長度。(\(z\) 函式)
這是本題的第一問,也是第二問的輔助陣列。
求法
顯然 \(next_0=|T|\),定義 \(n=|T|\)
考慮求出了 \(next_{0\cdots x-1}\),現在要求 \(next_x\)。
注:以下的圖顏色相同的部分的字串都相等。
定義 \(k=\max\{j+next_j-1\}(0\le j\lt x)\) 的 \(j\),\(p=k+next_k-1\)。
根據 \(next\) 定義,可得 \(T\) 的字首 \(T_{0\cdots next_k-1}=T_{k\cdots n}\) 的字尾 \(T_{k\cdots p}\)。
我們在 \(T_{k\cdots p}\) 中把 \(T_{x\cdots p}\) 標註出來,因為兩段灰色部分相同,可以在第一段裡找到相同的位置 \(T_{x-k\cdots next_k-1}\)
我們找到 \(next_{x-k}\),記作 \(y\)。由於 \(next\) 的定義,\(T_{0\cdots y-1}=T_{x-k\cdots x-k+y-1}\),在右邊的灰色部分,也有一段和它完全一樣的 \(T_{x\cdots x+y-1}\)。
情況一:藍色部分小於等於紅色部分,此時確定 \(next_x=y\)
情況二:藍色部分大於紅色部分,此時發現 \(x+y-1\) 已經超過了我們目前去到的最遠的地方,因此從 \(p,p-x+1\)(就是第一段藍色中位置大於紅色長度的那一部分) 開始一位一位比,算出真正的 \(next_x\)。(記得要更新 \(k\) 和 \(p\)
\(p\) 是我們當前已匹配到最遠的位置,情況一 \(k\) 不會變,情況二 \(k\) 會變大,\(k\) 保持不降,時間複雜度 \(\mathcal O(|T|)\)
\(extend\) 陣列
\(extend_i\) 陣列表示 \(S\) 從 \(i\) 開始的字尾與 \(T\) 的最長公共字首長度。(第二問)
求法
和 \(next\) 求法非常像。還是考慮求出了 \(extend_{0\cdots x-1}\),現在求 \(extend_x\)
還是定義 \(k=\max\{j+extend_j-1\}(0\le j\lt x)\) 的 \(j\),\(p=k+extend_k-1\)。
在圖中畫出 \(x\),\(x-k\),標紅色。
定義 \(y=next_{x-k}\),標出相同的三段藍色。
這時還是可以分為兩種情況。藍色部分小於等於紅色部分,存在 \(extend_x=y\),否則需要從 \(p,p-x+1\) 開始一一比對確定 \(extend_x\)。
由於此時的 \(k\) 不降,時間複雜度 \(\mathcal O(|S|)\)
實現
我的程式碼中 \(next\to nxt,extend\to ext\)
#include<stdio.h>
#include<string.h>
const int maxn = 2e7 + 1;
const int& max2(const int& a,const int& b){return a > b ? a : b;}
char s[maxn],t[maxn];
int n,m,nxt[maxn],ext[maxn];
void calc_nxt(){
nxt[0] = n;
int j = 0;
while(j + 1 < n && t[j] == t[j + 1]) ++j;
nxt[1] = j; // nxt[0],nxt[1] 暴力算
int k = 1; // 我是把k放在迴圈外,p用k得到,更新的也是k
for(int i = 2;i < n;++i){
int p = k + nxt[k] - 1;
if(i + nxt[i - k] <= p) nxt[i] = nxt[i - k]; // 情況一
else {
j = max2(p - i + 1,0); // 情況二
while(i + j < n && t[i + j] == t[j]) ++j; // 情況二,暴力一位一位比對
nxt[i] = j,k = i; // 記得更新k
}
}
}
void calc_ext(){// 和剛才幾乎一樣
int j = 0;
while(j < n && j < m && s[j] == t[j]) ++j;
ext[0] = j; // ext[0] 暴力算
int k = 0; // 放在迴圈外
for(int i = 1;i < m;++i){
int p = k + ext[k] - 1;
if(i + nxt[i - k] <= p) ext[i] = nxt[i - k];// 省略講解裡的y
else {
j = max2(p - i + 1,0); // 取max
while(i + j < m && j < n && s[i + j] == t[j]) ++j; // 一位一位比對
ext[i] = j,k = i;
}
}
}
int main(){
scanf("%s%s",s,t);
n = strlen(t),m = strlen(s);
calc_nxt();calc_ext();
long long res1 = 0,res2 = 0;
for(int i = 0;i < n;++i) res1 ^= 1LL * (i + 1) * (nxt[i] + 1);
for(int i = 0;i < m;++i) res2 ^= 1LL * (i + 1) * (ext[i] + 1);
printf("%lld\n%lld\n",res1,res2);
return 0;
}
// 拜拜qwq~ (你以為我會讓你直接複製程式碼?