1. 程式人生 > 其它 >擴充套件 KMP 學習筆記

擴充套件 KMP 學習筆記

P5410 【模板】擴充套件 KMP(Z 函式)

前置芝士

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~ (你以為我會讓你直接複製程式碼?