AcWing 831. KMP字符串
按照惯例,
今回是MOGE子镇楼。
今天是KMP算法,感觉理解还是不太扎实,写篇博客记录一下。
题目:
给定一个模式串 SS,以及一个模板串 PP,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 PP 在模式串 SS 中多次作为子串出现。
求出模板串 PP 在模式串 SS 中所有出现的位置的起始下标。
输入格式
第一行输入整数 NN,表示字符串 PP 的长度。
第二行输入字符串 PP。
第三行输入整数 MM,表示字符串 SS 的长度。
第四行输入字符串 SS。
输出格式
共一行,输出所有出现位置的起始下标(下标从 00 开始计数),整数之间用空格隔开。
数据范围
1≤N≤1051≤N≤105
1≤M≤1061≤M≤106
输入样例:
3
aba
5
ababa
输出样例:
0 2
暴力我们就不解释了,直接进入KMP。
KMP就是对字符串进行一系列匹配,从而减少不必要的操作,减小复杂度。
如何匹配呢,这就是我们的问题。KMP的做法就是找到字串的最大相同前后缀,从而进行匹配。
我们先写出我们能写出的代码:
#include<cstdio> const int maxn = 1e5 + 10; char S[maxn*10], P[maxn]; int main() { int ls, lp; scanf("%d%s", &lp, P+1); scanf("%d%s", &ls, S+1); return 0; }
我们要将P在S内作为子串出现的开始下标输出。
我们可以思考一下,我们在求字串最长相同前后缀时,和我们在求P在S内作为子串出现也是同一个问题。(此时P与S作为同一个字符串看待)
现在问题就是如何快速的求出字串最长相同前后缀。
所以我们令ne[n]为P中以第n位为结尾的字串最长相同前后缀。(只用对P串求字串最长相同前后缀)
所以我们又有代码:
int ne[maxn];
接下来就是求ne的过程了。
我们先贴出代码:
(P与S均于下标为1开始)
for (int i = 2, j = 0; i <= lp; ++i) { while (j && P[i] != P[j + 1])j = ne[j]; if (P[i] == P[j + 1])++j; ne[i] = j; }
在这里我们先扔出关于字串最长相同前后缀的定义:
假如A[]:abababf
看它的整体,则其
全部前缀为:
a,ab,aba,abab,ababa,ababab,ababab
全部后缀为:
f,bf,abf,babf,ababf,bababf,bababf
其前后缀长度不能为原串长
因此我们就有一个很明显的事实"ne[1]=0"。(至于为什么从i=2的原因,咱还没有搞(
我们以动态规划的视角观察这个东西,考虑能不能用ne[i-1]推出ne[i]。
ne[i]代表了以第i个字符结尾的字串最长相同前后缀大小。
当取到i时,也说明了前i个字符的字串也匹配成功,因此ne[0,i]有解。
---------------------------------------------------------------------
For()
{
我们设立一个指针j,j指向与ne[i-1]匹配的左串末尾。
因此我们也确保了有P[0,j]==P[i-1-j,i-1]。
(1)当P[j+1]==P[i]时:
有P[0,j+1]==P[i-j-1,i],也就是ne[i]=j+1;
break;
(2)当P[j+1]!=P[i]时:
也就没有P[0,j+1]==P[i-j-1],因此对于ne[i]的求解仍需要循环求解(此后j变为ne[ne[i-1]]匹配的左串末端),直至答案求出
(3)ne[i]=0;
}
---------------------------------------------------------------------
根据以上的思想,我们就可以得出以下代码:
#include<cstdio> const int maxn = 1e5 + 10; char S[maxn*10], P[maxn]; int ne[maxn]; int main() { int ls, lp; scanf("%d%s", &lp, P+1); scanf("%d%s", &ls, S+1); for (int i = 2, j = 0; i <= lp; ++i) { while (j && P[i] != P[j + 1])j = ne[j]; if (P[i] == P[j + 1])++j; ne[i] = j; } for (int i = 1, j = 0; i <= ls; ++i) { while (j && S[i] != P[j + 1])j = ne[j]; if (S[i] == P[j + 1])++j; if (j == lp) { printf("%d ", i - lp); j = ne[j]; } } return 0; }