字符串算法总结(kmp&&manacher&&trie&&AC自动机)
更博的目的是让自己学会AC自动机qwq
我太菜了连AC自动稽都不会qwq
KMP算法:
\(KMP\) 算法可以在线性时间内完成字符串匹配,对于每次失配之后,都不会从头重新开始枚举,而是根据已经得知的数据,从某个特定的位置开始匹配;而对于模式串的每一位,都有唯一的“特定变化位置”,这个在失配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约时间。
具体操作也很简单,首先预处理出一个 \(nex\) 数组,\(nex[i]\) 表示模式串的第 \(i\) 位往前 \(nex[i]\) 位与模式串第一位往后 \(nex[i]\) 位相同,注意此处 \(nex[i]\ !=i\) 。比如:
模式串:ababacbab
ababacbab
所以nex[5]=3
那么,这个数组有什么用呢?举个栗子:
模式串:ababacbab
文本串:abababacbab
传统做法是失败就从头开始:
文本串:abababacbab
已匹配:ababac -->失败
文本串:abababacbab
已匹配: a -->失败
文本串:abababacbab
已匹配: ababacbab -->成功
但是如果有着这个 \(nex\) :
文本串:ab(aba)bacbab
已匹配:(aba)bac -->失败
由于到第五位都是成功匹配,且nex[5]=3
所以我们可以直接从模式串的第三位开始
文本串:ab(aba)bacbab
已匹配: (aba)bacbab -->成功&&括号内的无需重新匹配
所以只要我们有了 \(nex\) 数组就可以提速了,另外如果需要从头开始,那么 \(nex\) 的值为 0 。
如何求出 \(nex\) 数组?具体解释见代码:
//核心思路就是自己和自己匹配
for(int i=2,j=0;i<=n2;i++)
{//i=2是因为要从模式串的第二位开始尝试和第一位匹配
//j代表往前匹配了多少位
while(j>0&&b[i]!=b[j+1]) j=nex[j];
//匹配失败就往前跳,直到j=0
if(b[i]==b[j+1]) j++;//成功就+1
nex[i]=j;
}
匹配文本串和模式串也是一样的
for(int i=1,j=0;i<=n1;i++)
{//i要从文本串的第一位开始尝试
//j代表模式串往前匹配了多少位
while(j&&a[i]!=b[j+1]) j=next[j];//同上
if(a[i]==b[j+1]) j++;//成功就+1
if(j==n2){}//即模式串往前n2位都已匹配
}
最后附上模板题和代码:
#include<bits/stdc++.h>
using namespace std;
long long next[1000001],n1,n2;
char b[1000001],a[1000001];
int main(){
scanf("%s %s",a+1,b+1);
next[1]=0;
n1=strlen(a+1);
n2=strlen(b+1);
for(int i=2,j=0;i<=n2;i++)
{
while(j>0&&b[i]!=b[j+1]) j=next[j];
if(b[i]==b[j+1]) j++;
next[i]=j;
}
for(int i=1,j=0;i<=n1;i++)
{
while(j&&a[i]!=b[j+1]) j=next[j];
if(a[i]==b[j+1]) j++;
if(j==n2) cout<<i-n2+1<<endl;
}
for(int i=1;i<=n2;i++) cout<<next[i]<<" ";
return 0;
}
Manacher算法:
既然zyz已经写的比较详细了,我也就懒得写了,直接放链接吧qwq
Trie树:
trie树就是一颗树,只不过是对字符建树罢了。
口胡一下吧:和其他树一样,Trie树也有节点,只不过节点就是节点,只存有儿子节点,以及到这个儿子节点的边上的信息,一般是字符或数字。
举个例子:
我们有 \(cab,cos,car,cat,cate,rain\) 这六个单词,我们要对它们建一棵Trie树,那么操作就是:
首先插入单词 \(cab\) :
接下来是 \(cos\) ,此时我们已经有了一个 \(c\) 节点,所以可以直接在下面插入:
同样,我们要插入一个字符时只要判断这个节点是否存在,有就接下去,没有就再开一个节点。最后的结果应该是这样的(单词表示为红点):
具体代码由于博主太懒,不想解释,另外推荐一道题:
洛谷P4551 最长异或路径 以及我写的题解(Trie树代码在这篇博客中有解释)
AC自动机:
先咕着qwq