题目地址:
https://www.luogu.com.cn/problem/P3375
题目描述:
给出两个字符串 s 1 s_1 s1和 s 2 s_2 s2,若 s 1 s_1 s1的区间 [ l , r ] [l, r] [l,r]子串与 s 2 s_2 s2完全相同,则称 s 2 s_2 s2在 s 1 s_1 s1中出现了,其出现位置为 l l l。现在请你求出 s 2 s_2 s2在 s 1 s_1 s1中所有出现的位置。定义一个字符串 s s s的border为 s s s的一个非 s s s本身的子串 t t t,满足 t t t既是 s s s的前缀,又是 s s s的后缀。对于 s 2 s_2 s2,你还需要求出对于其每个前缀 s ′ s' s′的最长border的长度。
输入格式:
第一行为一个字符串,即为 s 1 s_1 s1。第二行为一个字符串,即为 s 2 s_2 s2。
输出格式:
首先输出若干行,每行一个整数,按从小到大的顺序输出 s 2 s_2 s2在 s 1 s_1 s1中出现的位置。最后一行输出 ∣ s 2 ∣ ∣s_2∣ ∣s2∣个整数,第 i i i个整数表示 s 2 s_2 s2的长度为 i i i的前缀的最长border长度。
数据范围:
本题采用多测试点捆绑测试,共有 3 3 3个子任务。
Subtask1(30 points): ∣ s 1 ∣ ≤ 15 ∣s_1∣≤15 ∣s1∣≤15, ∣ s 2 ∣ ≤ 5 ∣s_2∣≤5 ∣s2∣≤5。
Subtask2(40 points): ∣ s 1 ∣ ≤ 1 0 4 ∣s_1∣≤10_4 ∣s1∣≤104, ∣ s 2 ∣ ≤ 1 0 2 ∣s_2∣≤10^2 ∣s2∣≤102。
Subtask3(30 points):无特殊约定。
对于全部的测试点,保证 1 ≤ ∣ s 1 ∣ , ∣ s 2 ∣ ≤ 1 0 6 1 \leq |s_1|,|s_2| \leq 10^6 1≤∣s1∣,∣s2∣≤106, 1 ≤ ∣ s 1 ∣ , ∣ s 2 ∣ ≤ 1 0 6 1≤∣s_1∣,∣s_2∣≤10^6 1≤∣s1∣,∣s2∣≤106, s 1 , s 2 s_1, s_2 s1,s2中均只含大写英文字母。
参考https://blog.csdn.net/qq_46105170/article/details/113805346。由于这里需要求border长度,所以要用非优化版的KMP,并且next数组要多开一位。同时这里下标从 1 1 1开始,所以要将匹配的下标加 1 1 1后输出。代码如下:
#include <iostream>
#include <vector>
using namespace std;
vector<int> buildNe(string p) {
int m = p.size();
vector<int> ne = vector<int>(m + 1, 0);
for (int i = 0, j = ne[0] = -1; i < m; )
if (j == -1 || p[i] == p[j]) ne[++i] = ++j;
else j = ne[j];
return ne;
}
int main() {
string s, p;
cin >> s >> p;
auto ne = buildNe(p);
for (int i = 0, j = 0; i < s.size(); ) {
if (j == -1 || s[i] == p[j]) i++, j++;
else j = ne[j];
if (j == p.size()) {
// 下标加1输出
printf("%d\n", i - j + 1);
// 假装最后一位没有匹配
i--;
j = ne[--j];
}
}
for (int i = 1; i <= p.size(); i++) printf("%d ", ne[i]);
return 0;
}
时间复杂度 O ( l s 1 + l s 2 ) O(l_{s_1}+l_{s_2}) O(ls1+ls2),空间 O ( l s 2 ) O(l_{s_2}) O(ls2)。