初探字符串匹配
K M P KMP KMP算法
模式串匹配,就是给定一个需要处理的文本串(理论上应该很长)和一个需要在文本串中搜索的模式串(理论上长度应该远小于文本串),查询在该文本串中,给出的模式串的出现有无、次数、位置等。
K
M
P
KMP
KMP 的精髓在于,对于每次失配之后,我都不会从头重新开始枚举,而是根据我已经得知的数据,从某个特定的位置开始匹配;
而对于模式串的每一位,都有唯一的“特定变化位置”,这个在失配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约时间。
next数组处理
用 n e x t next next 数组记录到它为止的模式串前缀的真前缀和真后缀最大相同的位置
然而这个地方我们要考虑“模式串前缀的前缀和后缀最大相同的位置”原因在于,我们需要用到next数组换位时,当且仅当未完全匹配
我们的操作只是针对模式串的前缀
毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”
KMP 中前后缀不包括模式串本身,即只考虑真前缀和真后缀,因为模式串本身需要整体考虑,当且仅当匹配完整个串之后
对自己匹配即可
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0; //第一个直接跳到1
for (int i = 2; i <= len; i++) {
while (k && p[i] != p[k + 1])k = Next[k]; //找到可匹配的位置
if (p[k + 1] == p[i]) k++;
Next[i] = k; //记录匹配的位置
}
}
kmp
int kmp(int n, int m) {
int i, j = 0;
for (int i = 1; i <= n; i++) {
while (j && s[i] != p[j + 1])j = Next[j];
if (p[j + 1] == s[i]) j++;
if (j == m) {
printf("%d\n", i - m + 1);
j = Next[j];
}
}
}
HDU1711 Number Sequence
题意:
两个数组匹配
思路:
板子题+1
POJ3461 Oulipo
题意:
字符串匹配
思路:
板子题+2
n e x t next next数组的应用
前缀的 前缀和后缀的最长公共长度
next代表前缀的 前缀和后缀的最长公共长度
HDU2594 Simpsons’ Hidden Talents
题意:
s
1
s1
s1的前缀和
s
2
s2
s2的后缀的最长公共长度
思路:
合并
s
1
s1
s1和
s
2
s2
s2
求
s
1
+
s
2
s1+s2
s1+s2的next数组
求最后一个字符的next位置
注意next位置要小于len1或len2
子串的循环
- 字符编号从0开始,那么 i f ( i if(i%(i-next[i])==0) if(i,则i前面的串为一个轮回串,其中轮回子串出现 i / ( i − n e x t [ i ] ) i/(i-next[i]) i/(i−next[i])次。
- next数组往前跳的步长是一样的
- 周期性字符串⇔ n m o d    ( n − n e x t [ n ] ) = 0 n \mod (n−next[n]) =0 nmod(n−next[n])=0 && n e x t [ n ] ! = 0 next[n] != 0 next[n]!=0,循环节长度是 n − n e x t [ n ] n−next[n] n−next[n]
HDU1358 Period
题意:求循环前缀
思路:板子题+1
POJ2406 Power Strings
题意:求字符串是否有一个字符串循环得到
思路:板子题+2
m a n a c h e r manacher manacher算法
- 对于一个回文串,有且仅有一个对称中心。且叫它回文对称中心。
- 在一个回文串内,任选一段区间 X ,一定存在关于"回文对称中心"对称的一个区间,且把这个区间叫做关于区间_X_的对称区间。
- 区间和对称区间一定全等
- 若一个区间的对称区间是回文串,这个区间必定是一个回文串。在大的回文串内,它们回文半径相等
- 然而我们通过确定关系预先得到的回文半径,它的数值,必定小于等于这个位置真实的回文串半径
记录这些数据到p数组。同时记录一个mid,一个r,分别代表 已经确定的右侧最靠右的回文串的对称中心和右边界。
若当前扫描到的位置为i,若mid<=i<=r,则我们可以找到它的一个对称点
这个点的位置是多少?是
m
i
d
×
2
−
i
mid×2-i
mid×2−i
- 若扩展一个新的关于该字符的回文半径,可以先确定一部分P[i]。
- 我们能确定的范围,其右侧不得大于r,即:p[i]+i<=r 移项得:p[i] <= r-i
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 11000005;
char s[maxn << 1];
int p[maxn << 1], cnt = 1;
void Read() {
char ch = getchar();
s[0] = '~'; s[1] = '|';
while (ch<'a' || ch>'z')
ch = getchar();
while (ch >= 'a' && ch <= 'z') {
s[++cnt] = ch;
s[++cnt] = '|';
ch = getchar();
}
return;
}
int main() {
int ans, r, mid;
Read();
ans = r = mid = 0;
for (int i = 1; i <= cnt; i++) {
if (i < r) p[i] = min(p[(mid << 1) - i], r - i);
else p[i] = 1;
while (s[i - p[i]] == s[i + p[i]])
p[i]++;
if (p[i] + i > r) r = p[i] + i, mid = i;
ans = max(ans, p[i]);
}
printf("%d\n", ans - 1);
}
HDU3068 最长回文
题意:求最长回文串长度
思路:板子题+1
BZOJ2342 双倍回文
题意:回文子串,前半串为回文串,后半串也是回文串
思路:
先跑一遍板子,当加入新的子串,检测前半串是否为回文串即可
由于回文串是四的倍数,回文串的对称中心为
∣
|
∣,前半串的对称中心也为
∣
|
∣
if (p[i] + i > r) {
if (s[i] == '|') {
int limit = max(r, i + 4);
for (int j = i + (p[i] - 1) / 4 * 4; j >= limit; j -= 4) {
if (p[i - (j - i) / 2] >= (j - i) / 2) {
ans = max(ans, j - i);
break;
}
}
}
r = p[i] + i, mid = i;
}
例题代码
Number Sequence
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1000005;
const int maxm = 10005;
int s[maxn], p[maxm];
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0;
for (int i = 2; i <= len; i++) {
while (k && p[i] != p[k + 1])k = Next[k];
if (p[k + 1] == p[i]) k++;
Next[i] = k;
}
}
int kmp(int n, int m) {
int i, j = 0;
for (int i = 1; i <= n; i++) {
while (j && s[i] != p[j + 1])j = Next[j];
if (p[j + 1] == s[i]) j++;
if (j == m) {
return i - m + 1;
//j = Next[j];
}
}
return -1;
}
int main() {
int t, n, m; scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", &s[i]);
for (int i = 1; i <= m; i++)scanf("%d", &p[i]);
GetNext(m);
printf("%d\n", kmp(n, m));
}
}
Oulipo
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1000005;
const int maxm = 1000005;
char s[maxn], p[maxm];
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0;
for (int i = 2; i <= len; i++) {
while (k && p[i] != p[k + 1])k = Next[k];
if (p[k + 1] == p[i]) k++;
Next[i] = k;
}
}
int kmp(int n, int m) {
int i, j = 0, cnt = 0;
for (int i = 1; i <= n; i++) {
while (j && s[i] != p[j + 1])j = Next[j];
if (p[j + 1] == s[i]) j++;
if (j == m) {
cnt++;
j = Next[j];
}
}
return cnt;
}
int main() {
int t; scanf("%d", &t);
while (t--) {
scanf("%s%s", p + 1, s + 1);
int n = strlen(s + 1), m = strlen(p + 1);
GetNext(m);
printf("%d\n", kmp(n, m));
}
}
Simpsons’ Hidden Talents
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 100005;
const int maxm = 100005;
char s[maxn], p[maxm];
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0;
for (int i = 2; i <= len; i++) {
while (k && s[i] != s[k + 1])k = Next[k];
if (s[k + 1] == s[i]) k++;
Next[i] = k;
}
}
int main() {
while (~scanf("%s%s", s + 1, p + 1)) {
int n = strlen(s + 1);
int m = strlen(p + 1);
strcat(s + 1, p + 1);
GetNext(n + m);
int k = n + m;
while (k > n || k > m)
k = Next[k];
if (k == 0) {
printf("0\n");
continue;
}
s[k + 1] = 0;
printf("%s %d\n", s + 1, k);
}
}
Period
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxm = 1000005;
char p[maxm];
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0;
for (int i = 2; i <= len; i++) {
while (k && p[i] != p[k + 1])k = Next[k];
if (p[k + 1] == p[i]) k++;
Next[i] = k;
}
}
int main() {
int m, g = 0;
while (~scanf("%d", &m)&&m) {
scanf("%s", p + 1);
GetNext(m);
printf("Test case #%d\n", ++g);
for (int i = 2; i <= m; i++) {
if (Next[i]&&i % (i - Next[i]) == 0)
printf("%d %d\n", i, i / (i - Next[i]));
}
printf("\n");
}
}
Power Strings
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxm = 1000005;
char p[maxm];
int Next[maxm];
void GetNext(int len) {
int k = 0;
Next[0] = Next[1] = 0;
for (int i = 2; i <= len; i++) {
while (k && p[i] != p[k + 1])k = Next[k];
if (p[k + 1] == p[i]) k++;
Next[i] = k;
}
}
int main() {
int m;
while (~scanf("%s", p + 1) && p[1] != '.') {
m = strlen(p + 1);
GetNext(m);
if (m % (m - Next[m]) == 0)printf("%d\n", m / (m - Next[m]));
else printf("1\n");
}
}
最长回文
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 11000005;
char s[maxn << 1];
int p[maxn << 1], cnt = 1;
void Read(char ch) {
s[0] = '~'; s[1] = '|';
while (ch<'a' || ch>'z')
ch = getchar();
while (ch >= 'a' && ch <= 'z') {
s[++cnt] = ch;
s[++cnt] = '|';
ch = getchar();
}
return;
}
int main() {
int ans, r, mid; char ch;
while ((ch = getchar()) != EOF) {
cnt = 1;
Read(ch);
ans = r = mid = 0;
for (int i = 1; i <= cnt; i++) {
if (i < r) p[i] = min(p[(mid << 1) - i], r - i);
else p[i] = 1;
while (s[i - p[i]] == s[i + p[i]])
p[i]++;
if (p[i] + i > r) r = p[i] + i, mid = i;
ans = max(ans, p[i]);
}
printf("%d\n", ans - 1);
}
}
双倍回文
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 11000005;
char s[maxn << 1];
int p[maxn << 1], cnt = 1;
void Read() {
char ch = getchar();
s[0] = '~'; s[1] = '|';
while (ch<'a' || ch>'z')
ch = getchar();
while (ch >= 'a' && ch <= 'z') {
s[++cnt] = ch;
s[++cnt] = '|';
ch = getchar();
}
return;
}
int main() {
int ans, r, mid, n;
scanf("%d", &n);
Read();
ans = r = mid = 0;
for (int i = 1; i <= cnt; i++) {
if (i < r) p[i] = min(p[(mid << 1) - i], r - i);
else p[i] = 1;
while (s[i - p[i]] == s[i + p[i]])
p[i]++;
if (p[i] + i > r) {
if (s[i] == '|') {
int limit = max(r, i + 4);
for (int j = i + (p[i] - 1) / 4 * 4; j >= limit; j -= 4) {
if (p[i - (j - i) / 2] >= (j - i) / 2) {
ans = max(ans, j - i);
break;
}
}
}
r = p[i] + i, mid = i;
}
}
printf("%d\n", ans);
}