PS:Ubuntu下的WPS总觉得和Windows下编辑的文档的兼容性不好。。
字符串
AC自动机
构造:点A的失败指针指向的是与该结点等价的点B,B表示的字符串是A表示的字符串的suffix(1)(从0开始)。
失败指针指向的状态与本状态拥有最长公共后缀。
那么A的失败指针为A的父亲的失败指针的对应字符儿子,如果没有这个儿子就继续沿失败指针走。
那么代码就很好写了
// 伪代码
void ac_automaton()
{
static queue<node *> q;
q.clear(); q.push(root);
while (!q.empty())
{
node *u = q.front(); q.pop();
for (node *i : u->child)
{
if (u == root) i->fail = root;
else
{
Node *p = u->fail;
while (p && !p->child[i->character]) p = p->fail;
i->fail = p ? p->child[i->character] : root;
}
q.push(i);
}
}
}
KMP
KMP用于在目标串中匹配模板串,找到模板串在目标串中的位置。
算法目标是让目标串的指针不回溯(暴力算法有不断回溯目标串指针的弊端)。
任务是始终保持目标串和模板串的指针对应,保证目标串与模板串的指针左侧(不包括指针指向的位置)是完全匹配的
另外跳模板串的指针就相当于移动模板串了(认定指针是对齐的)。
next[j]表示j失配后,在模板串P[1..j-1]中,longest_common_prefix(suf(i),suf(1))最大的那个i。
比如字符串:"ABCDABD"
那么next[6]=2,即P[1..6]中,suf(5)=‘AB'与suf(1)的公共前缀最长='AB',长度为2。
算出next[7]的意义是什么?如果我们一直匹配匹配到位置7了,说明目标串在当前位置,匹配了[1..6],那么为了让目标串的指针不回溯,就要移动模板串的指针,使目标串与模板串的指针再次对应。我们就可以移动到next[7-1=6]=2+1=3位置重启匹配。
此时是:
目标串:ABCDABK
模板串:ABCDABD
第7位不匹配,加粗的表示指针位置
移动指针后:
目标串:ABCDABK
模板串: ABCDABD
这样就可以让目标串的指针不动,移动模板串了。而且保证了前面提到的任务。
为什么要这么移动呢?如果让指针不跳这么多,显然指针前是不匹配的,即冗余判断,不可能得到解;或者是匹配模板串的长度不是最优!
但是模板串的指针也不能跳太多,所以跳一次只能从当前跳到与lcp最大的前缀的位置,否则会少考虑一部分必要重新匹配的位置。
所以目标串T的指针只会移动|T|次。
求解next?
需要两个指针,分别表示当前求解next[j],以及j之前匹配到的前缀I的长度
譬如ABCDABD,这里下标从1开始,如果下标从0开始的话i、j和next数组统一减1就可以了。
1. i=0,j=1 next[1]=0
2. i=0,j=2 A!=B next[2]=0
3. i=0,j=3 A!=C next[3]=0
4. i=0,j=4 A!=D next[4]=0
5. i=1,j=5 A=A next[5]=1
6. i=2,j=6 B=B next[6]=2 (如果第7位不匹配了就跳到3继续尝试匹配,因为模板串的5~6和1~2是匹配的,不用再匹配一次了)
7. i=0,j=7 C!=D失配,尝试查找比i更前的已匹配的位置,即i=next[i]=0 next[7]=0
显然,next[j]=max{i}。由于我们约定了next[j]=前缀序号+1,所以从j跳到next[j]就是刚好当前准备尝试匹配的位置。
getFail:
for (i = 0, j = 2; j <= n; ++j) {
while (i && p[j] != p[i + 1]) i = next[i];
if (p[i + 1] == p[j]) ++i;
next[j] = i;
}
find:
for (i = 0, j = 0; j < m; ++j) {
while (i && t[j] != p[i + 1]) i = next[i];
if (t[j] == p[i + 1]) ++i;
if (i == n) // find out.
}
Manacher
void manacher(char *ch, char *a, int *p, int n) { // 从1开始
int mx = 0, id, i;
m = 2 * n + 1;
a[0] = '+'; a[1] = '#'; a[m + 1] = '-';
for (i = 1; i <= n; ++i) {
a[i * 2] = ch[i];
a[i * 2 + 1] = '#';
}
for (i = 1; i <= m; ++i) {
if (mx > i) p[i] = min(mx - i, p[2 * id - i]);
else p[i] = 1;
for (; a[i - p[i]] == a[i + p[i]]; ++p[i]);
if (p[i] + i > mx) mx = p[i] + i, id = i;
}
}
manacher算法通过在字符间插入'#',将偶长度的回文串转化为奇长度的回文串。