题目描述
给出两个字符串 s 1 s1 s1和 s 2 s2 s2,其中 s 2 s2 s2为 s 1 s1 s1的子串,求出 s 2 s2 s2在 s 1 s1 s1中所有出现的位置。
为了减少骗分的情况,接下来还要输出子串的前缀数组next。
输入输出格式
输入格式:
第一行为一个字符串,即为 s 1 s1 s1
第二行为一个字符串,即为 s 2 s2 s2
输出格式:
若干行,每行包含一个整数,表示 s 2 s2 s2在 s 1 s1 s1中出现的位置
接下来 1 1 1行,包括 l e n g t h ( s 2 ) length(s2) length(s2)个整数,表示前缀数组 n e x t [ i ] next[i] next[i]的值。
输入输出样例
输入样例:
ABABABC
ABA
输出样例:
1
3
0 0 1
说明/提示
时空限制: 1000 m s , 128 M 1000ms,128M 1000ms,128M
数据规模:
设 s 1 s1 s1长度为 N N N, s 2 s2 s2长度为 M M M
对于 30 % 30\% 30%的数据: N < = 15 , M < = 5 N<=15,M<=5 N<=15,M<=5
对于 70 % 70\% 70%的数据: N < = 10000 , M < = 100 N<=10000,M<=100 N<=10000,M<=100
对于 100 % 100\% 100%的数据: N < = 1000000 , M < = 1000000 N<=1000000,M<=1000000 N<=1000000,M<=1000000
样例说明:
所以两个匹配位置为
1
1
1和
3
3
3,输出
1
1
1和
3
3
3
算法
一种朴素的算法是对 s 1 s1 s1中的每个字符都尝试与 s 2 s2 s2进行匹配,如果成功则匹配下一个。这种算法的复杂度为 O ( n m ) O(nm) O(nm),显然会超时,我们需要找到另一种方法。
我们发现,对于串 s 1 ‘ ‘ a b a b a b a b c " , s 2 = ‘ ‘ a b a b c " s1``ababababc", s2=``ababc" s1‘‘ababababc",s2=‘‘ababc", s 2 s2 s2的前缀 ‘ ‘ a b a b " ``abab" ‘‘abab"在 s 1 s1 s1中反复出现了多次,此时再一个一个字母地匹配效率就非常的低。此时我们就需要 K M P KMP KMP算法,使得我们在找到当前字母为 s 2 s2 s2的一个前缀时,不用从头匹配,而是直接从失配处开始匹配。
假定我们现在匹配到 i i i处,在匹配 i + 1 i+1 i+1时失配
此时我们在
s
2
[
1...
i
]
s2[1...i]
s2[1...i]中寻找最长公共前后缀(即
‘
‘
a
b
a
b
"
``abab"
‘‘abab"中的
‘
‘
a
b
"
``ab"
‘‘ab"
与
‘
‘
a
b
"
与``ab"
与‘‘ab"),并将
s
2
s2
s2移到前缀最后位置,再继续进行匹配。
其中
s
2
s2
s2中每个位置
i
i
i的最长匹配前缀可以预处理为
p
r
e
[
i
]
pre[i]
pre[i](有的人写为
n
e
x
t
[
i
]
next[i]
next[i])。特别的,
p
r
e
[
0
]
=
−
1
pre[0]=-1
pre[0]=−1
实现:
匹配:
for (int i = 0, j = -1; i < n; ++i) { //i、j分别为s1、s2当前下标
while (j != -1 && A[i] != B[j + 1]) j = pre[j]; //未匹配且j不是-1则令j=pre[j]
if (A[i] == B[j + 1]) ++j; //匹配上则++j
if (j == m - 1) printf("%d\n", i - m + 2), j = pre[j]; //匹配整个串则输出
}
预处理:
pre[0] = -1; //可以看做s2与自身匹配
for (int i = 1, j = -1; i < m; ++i) { //i为当前下标,j为前缀最后下标
while (B[i] != B[j + 1] && j != -1) j = pre[j]; //当前串与前缀的下一个不匹配则令j=pre[j]
if (B[i] == B[j + 1]) ++j; //匹配上则++j
pre[i] = j; //当前的最长匹配前缀位置为j
}
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m;
int pre[1000005];
char A[1000005], B[1000005];
int main()
{
scanf("%s%s", A, B);
n = strlen(A);
m = strlen(B);
pre[0] = -1;
for (int i = 1, j = -1; i < m; ++i) {
while (B[i] != B[j + 1] && j != -1) j = pre[j];
if (B[i] == B[j + 1]) ++j;
pre[i] = j;
}
for (int i = 0, j = -1; i < n; ++i) {
while (j != -1 && A[i] != B[j + 1]) j = pre[j];
if (A[i] == B[j + 1]) ++j;
if (j == m - 1) printf("%d\n", i - m + 2), j = pre[j];
}
for(int i = 0; i < m; ++i)
printf("%d ", pre[i] + 1);
return 0;
}