KMP算法
从头到尾彻底理解KMP对于KMP算法的学习推荐这一篇,这里面不仅讲了KMP而且比较详细以外,还扩充了一部分BM,Sunday算法的知识。
对于KMP我个人的理解就是,求最长公共前缀。其中最重要的就是对next数组的理解,大多数题目也都是在对next的应用上做文章。
本弱鸡也是在学习的路上,毕竟好久不碰算法了,需要好好复习一下了。刚开始做,顺便记记笔记
1.前后缀最长公共长度
不知道这么叫合不合适。题型一般是给你两个字符串s1,s2,然后让你求s1的所有后缀中和s2的所有前缀中相同的且最长的长度是多少。这一类题目一般是要将s1连接到s2后面,然后直接求一遍next数组,那么求得next数组的最后一位就是答案。其中连接的时候最好在中间插上一个分隔符,防止会出现一些特殊情况。
next数组本质是求最长公共前缀,也就是整个字符串都在求到这个位置以此为结尾最长的与前缀公共部分的字符串长度。所以求到尾部最后一个,其实就是求出了整个字符串的后缀与前缀的公共部分的长度。
[牛客]Youhane Assembler
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;
const int manx = 3e5+5;
string str[manx];
int nex[manx];
void getNext(string _str) {
int j = 0, k = -1; nex[0] = -1;
while(j < _str.length()) {
if(k == -1 || _str[j] == _str[k]) {
nex[++j] = ++k;
} else {
k = nex[k];
}
}
}
int main() {
int n, q;
cin >> n;
for(int i = 1; i <= n; i++) cin >> str[i];
cin >> q;
while(q--) {
int l, r;
scanf("%d%d", &l, &r);
string tmp = str[r]+"!"+str[l];
getNext(tmp);
cout << nex[tmp.length()] << endl;
}
return 0;
}
2.统计字符串中某串出现的次数
这种很简单,就直接用kmp算法做就可以了,就是要注意一下KMP的写法,我当时因为写法不正确到时内部逻辑有点问题,但是又很难看出来,就一直WA。
[牛客]可爱即正义
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 1e6+5;
int pos1, pos2;
char pattern[] = "suqingnianloveskirito", str[maxn];
int nex[25];
void getNext() {
int j =0, k = -1, pLen = 21;
nex[0] = -1;
while(j < pLen) {
if(k == -1 || pattern[j] == pattern[k]) {
nex[++j] = ++k;
} else {
k = nex[k];
}
}
}
void KMP(int sLen) {
int i = 0, j = 0, cnt = 0, pLen = 21;
while(i < sLen) {
if(j == -1 || str[i] == pattern[j]) {
i++; j++;
if(j == pLen) {
j = 0; cnt++;
if(cnt == 1) pos1 = i;
else if(cnt == 2) pos2 = i-1;
}
} else {
j = nex[j];
}
}
if(cnt == 0) printf("Yes\n1 2\n");
else if(cnt == 1) printf("Yes\n%d %d\n", pos1, pos1-1);
else if(cnt == 2) printf("Yes\n%d %d\n", pos1, pos2);
else printf("No\n");
}
int main() {
getNext();
scanf("%s", str);
int sLen = strlen(str);
KMP(sLen);
return 0;
}
3.统计同一字符串中前后缀的公共部分
这个其实感觉起来和第一种有点像,但是做法根本不一样。毕竟是一个字符串嘛,而且是带有统计的,在给你个这几个长度的函数让你求啊之类的,总不能把这一个字符串一直拆啊拆,然后挨个试吧,不现实。
但是,这个其实和第一个也有点相似之处,因为你要找前后缀的公共部分,还要想办法利用上next数组。要求前后缀公共部分,其实就是要求前缀的后缀和后缀的前缀相同的部分嘛,我们的next数组求的还是最长公共前缀,那么应该能用在求后缀的前缀上的。这样说,很抽象,给个题目:
[牛客]Just A String
这个题目我们可以这么做,next数组可以利用在求后缀的前缀上,那么我们就可以两层循环,先遍历后缀,每遍历一个后缀就先固定后缀,将他视为一个模式串,去整个字符串里进行匹配。每一个出现匹配字符的时候,都说明可以出现一个新的前后缀组合方式,然后进行统计就可以了。可能我记录的不明白,但是总体思想就是两层循环,先固定一个为模式串,与整个串进行匹配,来找与前缀的组合方式。这种真的是只可意会不可言传,需要琢磨,笔者口才又不好,所以直接上代码:
#include <string>
#include <iostream>
using namespace std;
int nex[2050];
long long ans;
void getNext(const string& str) {
int pLen = str.size();
int j = 0, k = -1; nex[0] = -1;
while(j < pLen) {
if(k == -1 || str[j] == str[k]) nex[++j] = ++k;
else k = nex[k];
}
}
void solve(string match, string pattern) {
getNext(pattern);
int i = 0, j = 0;
while(i < match.size()) {
if(match[i] == pattern[j]) {
i++; j++;
ans ^= j*j*(pattern.size()-j)*(i-j);
} else if(j == -1) i++, j++;
else j = nex[j];
}
}
int main() {
int T;
cin >> T;
while(T--) {
ans = 0;
string str;
cin >> str;
for(int i = 0; i < str.size(); i++) solve(str, str.substr(i));
cout << ans << endl;
}
return 0;
}
(菜鸡暂时只做了这么多,如有不对之处,请各位大佬指正~~)