《P3375 【模板】KMP》
题目描述
给出两个字符串 s1 和 s2,若 s1 的区间 [l,r] 子串与 s2 完全相同,则称 s2 在 s1 中出现了,其出现位置为 l。
现在请你求出 s2 在 s1 中所有出现的位置。
定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2,你还需要求出对于其每个前缀 s′ 的最长 border t′ 的长度。
输入格式
第一行为一个字符串,即为 s1。
第二行为一个字符串,即为 s2。
输出格式
首先输出若干行,每行一个整数,按从小到大的顺序输出 s2 在 s1 中出现的位置。
最后一行输出 ∣s2∣ 个整数,第 i 个整数表示 s2 的长度为 i 的前缀的最长 border 长度。
输入输出样例
输入 #1复制
ABABABC ABA
输出 #1复制
1 3 0 0 1
说明/提示
样例 1 解释
。
对于 s2 长度为 3 的前缀 ABA
,字符串 A
既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。
数据规模与约定
本题采用多测试点捆绑测试,共有 3 个子任务。
- Subtask 1(30 points):∣s1∣≤15,∣s2∣≤5。
- Subtask 2(40 points):∣s1∣≤104,∣s2∣≤102。
- Subtask 3(30 points):无特殊约定。
对于全部的测试点,保证 1≤∣s1∣,∣s2∣≤106,s1,s2 中均只含大写英文字母。
代码实现:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 计算前缀函数(next数组)
vector<int> compute_prefix_function(const string& pattern) {
int m = pattern.length();
vector<int> next(m, 0);
int len = 0;
int i = 1;
while (i < m) {
if (pattern[i] == pattern[len]) {
len++;
next[i] = len;
i++;
} else {
if (len != 0) {
len = next[len - 1];
} else {
next[i] = 0;
i++;
}
}
}
return next;
}
// KMP字符串匹配算法
vector<int> kmp_search(const string& text, const string& pattern, const vector<int>& next) {
vector<int> positions;
int n = text.length();
int m = pattern.length();
int i = 0; // 文本的索引
int j = 0; // 模式的索引
while (i < n) {
if (pattern[j] == text[i]) {
j++;
i++;
}
if (j == m) {
positions.push_back(i - j + 1); // 存储出现位置(1-based)
j = next[j - 1];
} else if (i < n && pattern[j] != text[i]) {
if (j != 0) {
j = next[j - 1];
} else {
i++;
}
}
}
return positions;
}
int main() {
string s1, s2;
cin >> s1 >> s2;
// 计算前缀函数
vector<int> next = compute_prefix_function(s2);
// 执行KMP搜索
vector<int> positions = kmp_search(s1, s2, next);
// 输出出现位置(使用传统迭代器方式)
for (vector<int>::iterator it = positions.begin(); it != positions.end(); ++it) {
cout << *it << endl;
}
// 输出前缀函数结果
for (int i = 0; i < s2.length(); i++) {
cout << next[i] << (i == s2.length() - 1 ? "\n" : " ");
}
return 0;
}