单模字符串匹配算法(KMP, exKMP, manacher)
约定:本文字符串均从
1. KMP
1.1 前缀函数
给定一个长度为
- 若子串
有一对相等的真前缀与真后缀(即除了该子串本身的前缀和后缀)则 ,即为该真前缀的长度; - 如果不只有一个相等的,那么
就是其中最长的那一对的长度; - 如果没有相等的,那么
。
劝退地,
特别地,
1.2 计算前缀函数
考虑如何计算
假设我们已经求出了
1.3 KMP 匹配
得到模式串
若
时间复杂度
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e6+10;
char T[N], S[N];
int n, m, ne[N]; // ne为T的前缀函数
void get_next() { // 求ne数组(即前缀函数)
ne[1] = 0;
for (int i = 2, j = 0; i <= n; ++i) {
while (T[j+1] != T[i] && j) j = ne[j];
if (T[j+1] == T[i]) ne[i] = ++j;
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> S+1 >> T+1;
n = strlen(T+1), m = strlen(S+1);
get_next();
for (int i = 1, j = 0; i <= m; ++i) { // 匹配
while (T[j+1] != S[i] && j) j = ne[j];
if (T[j+1] == S[i]) ++j;
if (j == n) cout << i-n+1 << '\n', j = ne[j];
}
for (int i = 1; i <= n; ++i) cout << ne[i] << ' ';
cout << '\n';
return 0;
}
2. exKMP
2.1 Z 函数
给定一个长度为
劝退地,
特别地,
2.2 计算 Z 函数
考虑如何计算
对于
假设我们已经求出了
- 若
:根据 的定义,有 ,因此 。这时:
:即 对应的位置在 box 内,则 。 :即 对应的位置在 box 外,则 ,然后暴力枚举能够扩展的字符即可。
- 若
:暴力枚举能够扩展的字符。
计算完
2.3 exKMP 匹配
与计算 Z 函数的过程类似,但我们是在
时间复杂度
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2e7+10;
char T[N], S[N];
int n, m, z[N], p[N];
ll get_num(int a[], int n) {
ll ans = 0;
for (int i = 1; i <= n; ++i) ans ^= (ll)i * (a[i]+1);
return ans;
}
void get_z() { // 计算z函数
z[1] = n;
for (int i = 2, l = 0, r = 0; i <= n; ++i) {
if (i < r) z[i] = min(z[i-l+1], r-i+1);
while (z[i] <= n-i && T[i+z[i]] == T[1+z[i]]) z[i] ++;
if (i+z[i]-1 > r) r = i+z[i]-1, l = i;
}
}
void get_p() { // 计算p函数
for (int i = 1, l = 0, r = 0; i <= m; ++i) {
if (i < r) p[i] = min(z[i-l+1], r-i+1);
while (p[i] <= m-i && S[i+p[i]] == T[1+p[i]]) p[i] ++;
if (i+p[i]-1 > r) r = i+p[i]-1, l = i;
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> S+1 >> T+1;
n = strlen(T+1), m = strlen(S+1);
get_z(), get_p();
cout << get_num(z, n) << '\n' << get_num(p, m) << '\n';
return 0;
}
3. manacher 算法
为了避免分类讨论,我们可以在所有字符之间加入一个特殊字符 #
,并在字符串开头插入一个特殊字符 !
。
令
假设我们已经求出了
- 若
:则 在 box 内的对称点为 。这时:
:即 在 box 内,根据对称性有 。 。 :令 ,然后暴力枚举能够扩展的字符即可。
- 若
:暴力枚举能够扩展的字符。
计算
时间复杂度
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 3e7+10;
int n, idx, ans, d[N];
char S[N], T[N];
void build() { // 转换字符串
S[++ idx] = '!', S[++ idx] = '#';
for (int i = 1; i <= n; ++i) S[++ idx] = T[i], S[++ idx] = '#';
S[++ idx] = '^';
}
void get_d() { // 求出d数组
for (int i = 2, l = 0, r = 0; i < idx; ++i) {
int j = l + r - i;
if (i <= r) d[i] = min(d[j], r-i+1);
while (S[i+d[i]] == S[i-d[i]]) d[i] ++;
if (i+d[i] > r) r = i+d[i]-1, l = i-d[i]+1;
ans = max(ans, d[i]-1);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T+1;
n = strlen(T+1);
build(), get_d();
cout << ans << '\n';
return 0;
}
本文作者:Jasper08
本文链接:https://www.cnblogs.com/Jasper08/p/17487113.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步