擴展 KMP 學習筆記

P5410 【模板】擴展 KMP(Z 函數)數組

前置芝士

KMP

洛谷模板 & \(\texttt{My solution}\)函數

問題歸納

擴展 KMP:求出字符串 \(S\) 的全部後綴與 \(T\) 的最長公共前綴長度,時間複雜度 \(\mathcal{O}(|S|+|T|)\)spa

該解法思想與 KMP 相似,因此稱做擴展 KMP。code

解法

\(next\) 數組

定義 \(next_i\)\(T\)\(i\) 開始的後綴與 \(T\) 的最長公共前綴長度。(\(z\) 函數)blog

這是本題的第一問,也是第二問的輔助數組。字符串


求法

顯然 \(next_0=|T|\),定義 \(n=|T|\)get

考慮求出了 \(next_{0\cdots x-1}\),如今要求 \(next_x\)string

注:如下的圖顏色相同的部分的字串都相等。io

定義 \(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~ (你覺得我會讓你直接複製代碼?
相關文章
相關標籤/搜索