记
p
p
p为模式串,
a
a
a为文本串。
注意我将模式串开头加了一个空格,有助于对状态机的理解。
#include <iostream>
#include <cstring>
using namespace std;
const char *p = " QAQ";
const int lenp = 3;
const int N = 1e6 + 5;
int dp[lenp][256];//dp[i][j]指当处于i状态,遇到字符j时的下一个状态
char a[N];
int main()
{
int x = 0;
cin >> a + 1;
dp[0][p[1]] = 1;
//dp数组预处理
for (int i = 1; i < lenp; i++)
{
for (int j = 0; j < 256; j++)
{
if (j == p[i + 1])
dp[i][j] = i + 1;
else
dp[i][j] = dp[x][j];
}
x = dp[x][p[i + 1]];
}
int j = 0;
int lena = strlen(a + 1);
for (int i = 1; i <= lena; i++)
{
j = dp[j][a[i]];
if (j == lenp)
{
cout << i - lenp + 1 << '\n';
break;
}
}
return 0;
}
该算法最难理解的部分为
x
x
x,我们处理使
x
x
x状态为
i
i
i状态的影子状态,即两者具有相同的前缀。
当字符
j
=
p
[
i
+
1
]
j=p[i+1]
j=p[i+1]时,我们进行状态推进使
d
p
[
i
]
[
j
]
=
i
+
1
dp[i][j]=i+1
dp[i][j]=i+1;
当字符
j
!
=
p
[
i
+
1
]
j!=p[i+1]
j!=p[i+1]时,我们进行状态重置,去考虑
i
i
i的影子状态
x
x
x,当
x
x
x遇到字符
j
j
j时他会进行的操作,即
d
p
[
i
]
[
j
]
=
d
p
[
x
]
[
j
]
dp[i][j]=dp[x][j]
dp[i][j]=dp[x][j]。
考虑这样一个例子来理解
x
x
x的作用。
即
x
x
x的作用为:当遇到不匹配的字符时,跳回“上一个”状态看是否可以匹配,而不是直接回到起点。
考虑这样一个例子来理解更新
x
x
x的过程
仔细想发现
x
x
x的行为与
i
i
i十分类似,他的更新过程也是不断向前找他的影子,直到有一个影子的可以匹配字符j或者回到状态0。