从头到尾彻底理解KMP(2014年8月22日版)
Next[i]表示第i个字符之前的最大公共前后缀的长度
在跳转的过程当中又有定位坐标的作用,跳到匹配串的拥有相同的前缀的子串的后一个位置(因为下标从0开始)
ps:2020.12.3 好久没用kmp了果然忘了,这次请了大佬一对一辅导终于会了,大概分为以下几步
处理next数组:
匹配串 s ,当前位置 i ,当前位前一位 能够匹配到的前缀 的最后一位的 下标 j
(由于是前缀,所以s[1…j] 中的 j 也是前缀的长度)
每次循环都在问: s[ i ] 和 前缀s[ j ]的后一个位置上的字符 是否相等,
如果相等,长度+1
如果不相等,找上一个和 s[ i-1 ] 相匹配的位置,看这个位置的后一个位置上的字符是否和 s[ i ]相等,
寻找过程中,长度缩短,前缀缩短,
得到当前位置的next值,得到 s[1…i] 最长的公共前后缀的长度
如果板子下标从1开始,next的值甚至可以作为 s[i-1] 上一个相匹配的位置的下标
KMP板子1:下标从0开始
int Next[N];
//要从下标0开始存字符串
void getNext(string p) {//匹配串
int plen = p.length();
Next[0] = -1;
int k = -1;//前缀指针从-1开始
int j = 0;
while (j < plen - 1) {
if (k == -1 || p[k] == p[j]) {//p[k]表示前缀 p[j]表示后缀
k++;
j++;
if (p[j] != p[k]) {
Next[j] = k;
} else {
//如果 p[j]==p[k] 则失配后 p[next[j]]=p[k] 必然导致失配
//这是不允许的 所以要匹配 next[j]= next[k]
Next[j] = Next[k];
}
} else {
k = Next[k];
}
}
}
int Kmp(string s, string p) {//模板串 要匹配的串
int i = 0;
int j = 0;
int slen = s.length();
int plen = p.length();
while (i < slen && j < plen) {
if (j == -1 || s[i] == p[j]) {
i++;
j++;
} else {
j = Next[j];
}
}
if (j == plen) {// p串全部已经匹配上了
return i - j;//返回s当中p的位置
} else return -1;
}
KMP板子2:下标从1开始
struct KMP {
vector<int> s; // 匹配串 匹配串失配得到next[]数组
// 有些题目非字符串 也可以用kmp做 所以 用vector存
static const int _size = 1e5 + 10;
int next[_size], lenS;
void getNext() {
// 匹配串 下表从1开始
next[1] = 0;
for (int i = 2, j = 0; i <= lenS; i++) {
// 找当前位置前一个匹配好的 在前缀的 后一个位置
// 观察是否 和当前位置上的字符是一样的
while (j > 0 && s[i] != s[j + 1]) j = next[j];
if (s[i] == s[j + 1])j++;
next[i] = j;
}
}
// 匹配串 _s 要求下标从1开始
void init(const vector<int> &_s) {
lenS = _s.size() - 1;
s = _s;
getNext();
}
// 匹配串 _s 要求下标从1开始
void init(const string &_s) {
lenS = _s.size() - 1;
s = vector<int>(lenS + 1);
copy(_s.begin(), _s.end(), s.begin());
getNext();
}
// 在文本串t里 找匹配串s第一次出现的位置
// 若不存在 返回 -1
// 文本串 t 要求下标从1开始
int f[_size];// f[i] 表示 t[1...t] 与 s 能匹配的最长长度(s不一定都能匹配上)
int findS(const vector<int> &t) {
int lenT = t.size() - 1;
for (int i = 1, j = 0; i <= lenT; i++) {
while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
if (t[i] == s[j + 1]) j++;
f[i] = j;
if (f[i] == lenS) {
return i - lenS + 1;
}
}
return -1;
}
int findS(const string &_t) {
vector<int> t = vector<int>(_t.size());
copy(_t.begin(), _t.end(), t.begin());
return findS(t);
}
// 统计s[1...i]在t中出现的次数
// 求 前缀长度 * 前缀出现次数 之和
int val[_size];//val[1...i]表示前缀s[1...i]出现的次数
ll Count(const string &t) {
fill(val, val + lenS + 1, 0);
int lenT = t.size() - 1;
for (int i = 1, j = 0; i <= lenT; ++i) {
while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
if (t[i] == s[j + 1]) j++;
val[j]++; // 统计最长的前缀出现的次数
}
for (int i = lenS; i; --i) {
// 每一个 长前缀 里 都会有 短前缀 的存在
// 短前缀出现了多少次 需要从长前缀统计而来
val[next[i]] += val[i]);
}
ll res = 0;
for (int i = 1; i <= lenS; ++i) {
// 统计...
// as 题目要求 求 前缀长度 * 前缀出现次数 之和
res = Add(res, Mul(i, val[i]));
}
return res;
}
} kmp;
应用:板子对应的题目 - http://acm.hdu.edu.cn/showproblem.php?pid=1711
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;
struct KMP {
vector<int> s; // 匹配串 匹配串失配得到next[]数组
// 有些题目非字符串 也可以用kmp做 所以 用vector存
static const int _size = 1e5 + 10;
int next[_size], lenS;
void getNext() {
// 匹配串 下表从1开始
next[1] = 0;
for (int i = 2, j = 0; i <= lenS; i++) {
// 找当前位置前一个匹配好的 在前缀的 后一个位置
// 观察是否 和当前位置上的字符是一样的
while (j > 0 && s[i] != s[j + 1]) j = next[j];
if (s[i] == s[j + 1])j++;
next[i] = j;
}
}
// 匹配串 _s 要求下标从1开始
void init(const vector<int> &_s) {
lenS = _s.size() - 1;
s = _s;
getNext();
}
// 在文本串t里 找匹配串s第一次出现的位置
// 若不存在 返回 -1
// 文本串 t 要求下标从1开始
int f[N];// f[i] 表示 t[1...t] 与 s 能匹配的最长长度(s不一定都能匹配上)
int findS(const vector<int> &t) {
int lenT = t.size() - 1;
for (int i = 1, j = 0; i <= lenT; i++) {
while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
if (t[i] == s[j + 1]) j++;
f[i] = j;
if (f[i] == lenS) {
return i - lenS + 1;
}
}
return -1;
}
} kmp;
vector<int> s, t;
void Solve() {
cin >> n >> m;
s = vector<int>(1);
t = vector<int>(1);
/*s.clear();
t.clear();
s.push_back(0);
t.push_back(0);//占位*/
for (int i = 1, x; i <= n; i++) {
cin >> x;
t.push_back(x);
}
for (int i = 1, x; i <= m; i++) {
cin >> x;
s.push_back(x);
}
kmp.init(s);// 同时 处理Next数字
cout << kmp.findS(t) << endl;
}
void Run() {
int T;
cin >> T;
for (int cs = 1; cs <= T; cs++) {
//cout << "Case " << cs << ": ";//printf("Case %d: ", cs);
Solve();
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
freopen("debug.out", "w", stdout);
#endif
Run();
return 0;
}
统计后缀长度
×
\times
× 后缀在母串中出现次数之和
http://acm.hdu.edu.cn/showproblem.php?pid=6153
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll Add(ll a, ll b) {
a %= mod;
b %= mod;
return (a + b) % mod;
}
ll Mul(ll a, ll b) {
a %= mod;
b %= mod;
return a * b % mod;
}
struct KMP {
int lenS;
string s;
static const int _size = 2e6 + 10;
int next[_size];
void init(const string &_s) {
lenS = _s.size() - 1;
fill(next, next + lenS + 1, 0);
s = _s;
getNext();
}
void getNext() {
next[1] = 0;
for (int i = 2, j = 0; i <= lenS; ++i) {
while (j > 0 && s[i] != s[j + 1]) j = next[j];
if (s[i] == s[j + 1]) j++;
next[i] = j;
}
}
// 统计s[1...i]在t中出现的次数
ll val[_size];
ll Count(const string &t) {
fill(val, val + lenS + 1, 0);
int lenT = t.size() - 1;
for (int i = 1, j = 0; i <= lenT; ++i) {
while (j > 0 && (j == lenS || t[i] != s[j + 1])) j = next[j];
if (t[i] == s[j + 1]) j++;
val[j]++; // 统计最长的前缀出现的次数
}
for (int i = lenS; i; --i) {
// 每一个 长前缀 里 都会有 短前缀 的存在
// 短前缀出现了多少次 需要从长前缀统计而来
val[next[i]] = Add(val[next[i]], val[i]);
}
ll res = 0;
for (int i = 1; i <= lenS; ++i) {
// 题目要求 求 前缀长度 * 前缀出现次数 之和
res = Add(res, Mul(i, val[i]));
}
return res;
}
} kmp;
string s1, s2;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
freopen("debug.out", "w", stdout);
#endif
int T;
cin >> T;
while (T--) {
cin >> s1 >> s2;
reverse(s1.begin(), s1.end());
reverse(s2.begin(), s2.end());
s1 = " " + s1;
s2 = " " + s2;
kmp.init(s2);
cout << kmp.Count(s1) << endl;
}
}