资料内容:
https://oi-wiki.org/string/kmp/
很久以前学过,写一些笔记作复习资料
一些概念:
真前缀, 真后缀等等不作介绍
(真前后缀匹配函数)前缀函数(pi函数):
\[\pi[i] = \max_{k = 0 \dots i}\{k: s[0 \dots k - 1] = s[i - (k - 1) \dots i]\} \]特别规定,
\[\pi[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(n^3)\)来算, 我们看看怎么加速
引理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(n^2)\)的
首先if里面的substr函数是雷打不动的\(O(n)\)复杂度, 外面两层循环是\(O(n)\)的理由是这样: 两层循环不是independent的, 将前缀函数看作一个银行账户, 可以发现我们每天(每次循环)肯定固定花销是两块, 一次就配对成功, 则省了一块存银行里, 配对不成功, 会进行额外的花销, 但同时也会导致我们的"银行账户"下降, 也就是说可以看成把之前省的钱给花了, 那么摊下来就是最多\(2n - 2\)次花销, 也就是外面两层循环是\(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基本上就是前缀函数计算的一个直接应用
标签:std,string,substr,int,s1,一颓,KMP,pi From: https://www.cnblogs.com/IhopeIdieyoung/p/17442686.html