1. 程式人生 > 實用技巧 >演算法總結篇---KMP演算法

演算法總結篇---KMP演算法

寫在前面

僅為自用,不做推廣

一起來看貓片吧!

一篇不錯的部落格,然而我悶了一下午還是不會,看了看書算是搞懂了

部落格裡面各種性質講的非常詳細,有空可以回看一下

核心的兩段程式碼

nxt陣列預處理:

我這裡使用pre表示nxt陣列,用go表示要匹配的串

void init(){//預處理pre陣列
	int len = strlen(go + 1);
	int j = 0;
	for(int i = 1; i < len; ++i){
		while(j > 0 && go[i + 1] != go[j + 1]) j = pre[j];
		if(go[i + 1] == go[j + 1]) ++j;
		pre[i + 1] = j;
	}
}

原字串的匹配:

    for(int i = 0; i < len1; ++i){
		while(j > 0 && s[i + 1] != go[j + 1]) j = pre[j];
		if(s[i + 1] == go[j + 1]) ++j;
//		cout<<"i:"<<i<<" "<<j<<endl;
		if(j == len2){//如果匹配完成
			cnt++;
			j = 0;
		}
    }

例題

剪花布條

直接KMP匹配即可,匹配成功將匹配串的指標置為0

Radio Transmission

一個結論題,答案為 \(n - nxt[n]\),好像與nxt陣列本身的性質有關

OKR-Periods of Words

洛谷題解

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+6;
const int INF = 1;
const int mod = 1;

int n;
LL ans = 0;
char s[MAXN];
int pre[MAXN];

int read(){
	int s = 0, f = 0;
	char ch = getchar();
	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return f ? -s : s;
}

void init(){
	int j = 0;
	for(int i = 1; i <= n; ++i){
		while(j > 0 && s[i + 1] != s[j + 1]) j = pre[j];
		if(s[i + 1] == s[j + 1]) ++j;
		pre[i + 1] = j; 
	}
}

int main()
{
	n = read();
	cin >> (s + 1);
	init();
	for(int i = 1; i <= n; ++i){
		int j = i;
		while(pre[j]) j = pre[j];
		if(pre[i]) pre[i] = j;
		ans += (i - j);
	} 
	printf("%lld", ans);
	return 0;
}

似乎在夢中見過的樣子

看這位大佬的題解

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 2e4+6;
const int INF = 1;
const int mod = 1;

int n, k, cnt = 0; 
char s[MAXN];
int pre[MAXN];

int read(){
	int s = 0, f = 0;
	char ch = getchar();
	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return f ? -s : s;
}

void Kmp(int l){
	int j = l - 1; 
	pre[l] = pre[l - 1] = j;
	for(int i = l; i < n; ++i){
		while(j > l - 1 && s[j + 1] != s[i + 1]) j = pre[j];
		if(s[j + 1] == s[i + 1]) j++;
		pre[i + 1] = j;
	}
	for(int i = l; i < n; ++i){
		j = pre[i + 1];
		while(j > l - 1 && l + 2 * (j - l + 1) > i + 1) j = pre[j];
		if(j - l + 1 >= k) cnt++;
	}
}

int main()
{
	cin >> (s + 1);
	k = read();
	n = strlen(s + 1);
	for(int i = 1; i <= n; ++i) Kmp(i);
	printf("%d", cnt);
	return 0;
}

Censoring

主要思路是開一個棧,來儲存還未被消去的字串
如果一個串匹配完成,從彈出相應的串
在入棧是順便記錄入棧字元的失陪位置,匹配完一個串後可以直接從棧頂所對字元的失陪位置開始匹配

從前到後跑一遍即可,複雜度 \(O(n)\)

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e6+6;
const int INF = 1;
const int mod = 1;

char s[MAXN], t[MAXN];
int lens, lent;
int pre[MAXN], f[MAXN];
int stc[MAXN], sc = 0;

int read(){
	int s = 0, f = 0;
	char ch = getchar();
	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return f ? -s : s;
}

void init(){
	int j = 0;
	for(int i = 1; i <= lent; ++i){
		while(j && t[i + 1] != t[j + 1]) j = pre[j];
		if(t[i + 1] == t[j + 1]) ++j;
		pre[i + 1] = j;
	}
}

int main()
{
	cin >> (s + 1);
	cin >> (t + 1);
	lens = strlen(s + 1);
	lent = strlen(t + 1);
	init();
	for(int i = 0, j = 0; i < lens; ++i){
		while(j && s[i + 1] != t[j + 1]) j = pre[j];
		if(s[i + 1] == t[j + 1]) ++j;
		f[i + 1] = j;
		stc[++sc] = i + 1;
		if(j == lent){
			sc -= lent, j = f[stc[sc]];
		}
	}
	for(int i = 1; i <= sc; i++){
		printf("%c", s[stc[i]]);
	}
	return 0;
}