每天一颓: 均摊分析, pi函数和KMP算法

资料内容:
https://oi-wiki.org/string/kmp/


很久以前学过,写一些笔记作复习资料

一些概念:
真前缀, 真后缀等等不作介绍

(真前后缀匹配函数)前缀函数(pi函数):

π[i]=maxk=0i{k:s[0k1]=s[i(k1)i]}

特别规定,

π[0]=0


/*
Author: SJ
*/
#include<bits/stdc++.h>
const int N = 1e5 + 10;
using ll = long long;
using ull = unsigned long long;

std::string s1;
int pi[N];
void pi_query(std::string s1) {
	for (int i = 1; i < s1.size(); i++) {
		for (int j = i; j >= 0; j--) {
			if (s1.substr(0, j) == s1.substr(i - j + 1, j)) {
				pi[i] = j;
				break;
			}
		}
	}
}
int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	std::cin >> s1;
	pi_query(s1);
	for (int i = 0; i < s1.size(); i++) std::cout << pi[i] << ' ';
	return 0;
}

显然可以这样O(n3)来算, 我们看看怎么加速

引理1: 相邻的pi-function最多增加1
证明: 类似LCS的证明, 反证法即可

代码即可优化成

void pi_query(std::string s1) {
	for (int i = 1; i < s1.size(); i++) {
		for (int j = pi[i - 1] + 1; j >= 0; j--) {
			if (s1.substr(0, j) == s1.substr(i - j + 1, j)) {
				pi[i] = j;
				break;
			}
		}
	}
}

我们介绍amortized analysis(主要是会计方法)说明一下这样计算是O(n2)
首先if里面的substr函数是雷打不动的O(n)复杂度, 外面两层循环是O(n)的理由是这样: 两层循环不是independent的, 将前缀函数看作一个银行账户, 可以发现我们每天(每次循环)肯定固定花销是两块, 一次就配对成功, 则省了一块存银行里, 配对不成功, 会进行额外的花销, 但同时也会导致我们的"银行账户"下降, 也就是说可以看成把之前省的钱给花了, 那么摊下来就是最多2n2次花销, 也就是外面两层循环是O(n)


类似dp的思路, 我们可以往前跳转, 详情请看OI-Wiki,

那为什么这样子做直接就把复杂度变成惊人的O(n)了呢,我们还是觉得substr太慢了, 我们直接通过上面的性质把判真前后缀是否相等变成O(1), 最后就变成线性了

void pi_query(std::string s1) {
	for (int i = 1; i < s1.size(); i++) {
		int j = pi[i - 1];
		while (j > 0 && s1[i] != s1[j]) j = pi[j - 1];
		if (s1[i] == s1[j]) j++;
		pi[i] = j;
	}
}

kmp基本上就是前缀函数计算的一个直接应用

posted @   IHOPEIDIEYOUNG  阅读(74)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示