AC自动机
以 P5357 【模板】AC 自动机 为例。
AC自动机是用于进行多模板串的匹配。既然是多模板串,当然是在字典树上构建的。作为自动机,就是根据规则不断在图上游走,最终走到需要的点。
插入时正常插入,记录模板串。
void ins(int len,int idn){
int now=0;
for(int i=1;i<=len;i++){
int id=ch[i]-'a';
if(t[now].son[id]==0){
t[now].son[id]=++tot;
}
now=t[now].son[id];
}
t[now].v.push_back(idn);
}
想要优化到线性,核心在于失配时找到与后缀相同的前缀border。关键是 \(fail\) ,\(fail_i\) 指向当 \(i\) 的下一个节点失配时,最长border的从根节点出发的前缀末尾。如果失配,就找到 \(fail\) 的位置,思想和 KMP 一样。
求\(fail\) 。画图,父亲节点的 \(fail\) 的节点会有相同的前缀。如果 \(fail_{fa}\) 有跟 \(i\) 相同字符的儿子,则 \(fail_i\) 就是那个相同字符的儿子。
匹配时,如果某个节点有字符 \(c\) 的儿子,可以让文本串在图上跑动。如果没有字符 \(c\) 的儿子,此时算是失配,但仍然要在图上跑动。失配后要找到 \(fail\) 的位置,可以直接将字符 \(c\) 的儿子设为 \(fail\) 的儿子。
可以据此建立 AC自动机。 \(fail\) 是从下向上指,所以要从根开始构建 \(fail\) 。广搜。
void workf(){
queue<int>q;
for(int i=0;i<=25;i++){
if(t[0].son[i]!=0){
q.push(t[0].son[i]);
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<=25;i++){
int son=t[u].son[i],fail=t[u].fail;
if(son!=0){
t[son].fail=t[fail].son[i];
q.push(son);
}
else{
t[u].son[i]=t[fail].son[i];
}
}
}
for(int i=1;i<=tot;i++){
v[t[i].fail].push_back(i);
}
}
匹配。如果到某个节点匹配成功时,所有用 \(fail\) 连接的节点都有相同的后缀,他们都匹配成功了。但不能暴力向上跳匹配(会超时),可以构建用 \(fail\) 连接的树,匹配到了打个标记,最后向上统计。
void qur(int len){
int now=0;
for(int i=1;i<=len;i++){
int id=ch[i]-'a';
now=t[now].son[id];
f[now]++;
}
}
void dfs(int x){
for(auto i:v[x]){
dfs(i);
f[x]+=f[i];
}
for(auto i:t[x].v){
ans[i]=f[x];
}
}

浙公网安备 33010602011771号