AC自动机

简介: AC自动机

AC自动机模板

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+10;
char str[N];
int tr[N][2],tail[N],idx;
int q[N],ne[N];
void insert()//将子串连成tire
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int c=str[i]-'0';
        if(!tr[p][c]) tr[p][c]=++idx;
        p=tr[p][c];
    }
   //定义的tail的意义来操作
   tail[p]++;
}
void build()//将子串进行求next
{
    int hh=0,tt=-1;
    for(int i=0;i<2;i++)
        if(tr[0][i])
           q[++tt]=tr[0][i];
   while(hh<=tt)
   {
       int t=q[hh++];
       for(int i=0;i<2;i++)
       {
           int &p=tr[t][i];
           if(!p) p=tr[ne[t]][i];
           else
           {
               ne[p]=tr[ne[t]][i];
               q[++tt]=p;
               //定义tail的意义来操作
               tail[p]=tail[p]||tail[ne[p]];
           }
       }
   }
}
bool solve(int u)//子串跟母串要操作的
{
}

AC自动机=tire+kmp,AC自动机就是在tire里面建立kmp

kmp中next[i]数组的意思是在p串中,以p[i]结尾的后缀,能够匹配的从1开始的非平凡前缀的最大长度,简而言之,当前的后缀与最大的前缀匹配的最长长度

kmp中的求next的求法


用上一个位置的next更新我这一层的next

对应到tire中的kmp求法,就是用前一层的next信息更新我当前这一层的信息,然后一层一层的遍历用bfs来搜


把一维next对应到二维的next中

tire中的next数组的指向


1.搜索关键词

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

题意就是问最后的长的字符串中包含多少个前面输入的一系列字符串

AC自动机写法

#include<bits/stdc++.h>
using namespace std;
const int N=10010,S=55,M=1000010;
int n;
int tr[N*S][26],cnt[N*S],idx;
char str[M];
int q[N*S],ne[N*S];
void insert()//tire树
{
    int p=0;//从头结点开始插
    for(int i=0;str[i];i++)
    {
        int t=str[i]-'a';//映射到数字
        if(!tr[p][t]) tr[p][t]=++idx;//假如这个位置没有这个字母,则新开辟个节点
        p=tr[p][t];//跳到这个位置
    }
    cnt[p]++;//这个位置的单词++
}
void build()//求next数组的过程
{
    int hh=0,tt=-1;
    for(int i=0;i<26;i++)//把第一层的先入队
        if(tr[0][i])
           q[++tt]=tr[0][i];
    while(hh<=tt)
    {
        int t=q[hh++];//取出队头
        for(int i=0;i<26;i++)//枚举子节点
        {
            int c=tr[t][i];
            if(!c) continue;//假如这个位置没有这个节点,则直接跳
            int j=ne[t];
            while(j&&!tr[j][i]) j=ne[j];//假如匹配不成功则继续跳
            if(tr[j][i]) j=tr[j][i];//成功就为这个位置
            ne[c]=j;//这个节点的next更新一下
            q[++tt]=c;//把这个节点入队
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        //清空上一层的状态
        memset(tr,0,sizeof tr);
        memset(cnt,0,sizeof cnt);
        memset(ne,0,sizeof ne);
        idx=0;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%s",str);
            insert();//插入这个单词
        }
        build();//求next数组
        scanf("%s",str);
        int res=0;
        for(int i=0,j=0;str[i];i++)//next匹配的过程
        {
            int t=str[i]-'a';
            while(j&&!tr[j][t]) j=ne[j];//假如这个位置匹配不上,则继续跳
            if(tr[j][t]) j=tr[j][t];//假如匹配了,则等于这个位置
            int p=j;
            while(p)//枚举p所重复的单词
            {
                res+=cnt[p];
                cnt[p]=0;//把这个位置的单词清空
                p=ne[p];//继续枚举上一个单词
            }
        }
        printf("%d\n",res);
    }
    return 0;
}

AC自动机优化成tire图的写法,优化常数,其实就是把不匹配的直接一步跳到匹配的地方,则下次用就不用while求匹配的位置了,可以直接用

优化的位置是求next数组的位置跟next匹配的过程

void build()//求next数组的过程
{
    int hh=0,tt=-1;
    for(int i=0;i<26;i++)//把第一层的先入队
        if(tr[0][i])
           q[++tt]=tr[0][i];
    while(hh<=tt)
    {
        int t=q[hh++];//取出队头
        for(int i=0;i<26;i++)//枚举子节点
        {
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];//假如这个节点不存在,直接就跳到上一层的节点
            else
            {
                ne[p]=tr[ne[t]][i];//让这个节点等于上一层的节点
                q[++tt]=p;//放进队列里
            }
        }
    }
}
for(int i=0,j=0;str[i];i++)//next匹配的过程
        {
            int t=str[i]-'a';
            j=tr[j][t];//因为优化的是直接等于next跳完后的值,所以直接跳到这个位置
            int p=j;
            while(p)//枚举p所重复的单词
            {
                res+=cnt[p];
                cnt[p]=0;//把这个位置的单词清空
                p=ne[p];//继续枚举上一个单词
            }
        }1. void

2.修复DNA(状态机dp+AC自动机)

3691 -- DNA repair (poj.org)

题意相当于给定我们一系列字符串,然后最后给我们一个长的字符串,问我们至少改变这个字符串的多少个字母使得这个长的字符串不包含前面输入的一系列字符串


状态机的dp分析

#include<stdio.h>
#include<string.h>
#define min(a,b) a>b?b:a
const int N=1010;
int n,m;
int tr[N][4],dar[N],idx;//tr就是tire,dar就是标记这个单词是否存在
char str[N];
int q[N],ne[N];
int f[N][N];//f就是状态机dp的f函数
int get(char c)//把字母映射到数字里
{
    if(c=='A') return 0;
    if(c=='T') return 1;
    if(c=='G') return 2;
    return 3;
}
void insert()//tire的插入
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int t=get(str[i]);
        if(!tr[p][t]) tr[p][t]=++idx;
        p=tr[p][t];
    }
    dar[p]=1;//标记这个结尾的单词是存在的
}
void build()//求next数组
{
    int hh=0,tt=-1;
    for(int i=0;i<4;i++)//把第一层放进队列中
        if(tr[0][i])
          q[++tt]=tr[0][i];
    while(hh<=tt)
    {
        int t=q[hh++];//求出队头
        for(int i=0;i<4;i++)//枚举下一层的节点
        {
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];//假如这个节点不存在,则直接跳到存在的节点上
            else
            {
                ne[p]=tr[ne[t]][i];//存在则直接跳即可
                q[++tt]=p;
                dar[p]|=dar[ne[p]];//把这个单词标记为危险DNA序列
            }
        }
    }
}
int main()
{
    int T=1;
    while(scanf("%d",&n),n)
    {
        //清空上一层的状态
        memset(tr,0,sizeof tr);
        memset(dar,0,sizeof dar);
        memset(ne,0,sizeof ne);
        idx=0;
        for(int i=0;i<n;i++)
        {
            scanf("%s",str);
            insert();//插入这个单词
        }
        build();//求next数组
        scanf("%s",str+1);
        m=strlen(str+1);
        memset(f,0x3f,sizeof f);//因为要求最小值,则初始化为正无穷
        f[0][0]=0;//从0个开始匹配到AC自动机第0个字母最小修改0次
        for(int i=0;i<m;i++)//枚举串的长度
            for(int j=0;j<=idx;j++)//枚举所有危险的单词
               for(int k=0;k<4;k++)//枚举危险单词的字母
               {
                 int t=get(str[i+1])!=k;//看看这个单词是否等于第k个位置
                 int p=tr[j][k];//获取危险单词的这个位置的字母
                 if(!dar[p]) f[i+1][p]=min(f[i+1][p],f[i][j]+t);//假如不存在这个危险单词,则更新最小值
               }
        int res=0x3f3f3f3f;
        for(int i=0;i<=idx;i++) res=min(res,f[m][i]);//枚举所有需要更新的最小
        if(res==0x3f3f3f3f) res=-1;
        printf("Case %d: %d\n",T++,res);
    }
    return 0;
}

3.单词

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

相当于问其他子串里有多少个后缀与我这个串匹配。则反向递推求这个后缀匹配多少个前缀,相当于让f[i]出现的次数累加到f[next[i]]里面,按照拓扑序递推回去next【i】然后累加就行了

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int n;
int tr[N][26],f[N],idx;//f记录的是以这个结尾的单词的数量
int q[N],ne[N];
char str[N];
int id[210];
void insert(int x)//tire的插入
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int t=str[i]-'a';
        if(!tr[p][t]) tr[p][t]=++idx;
        p=tr[p][t];
        f[p]++;//因为记录的是前缀,则在内部就记录这个值了
    }
    id[x]=p;//
}
void build()
{
    int hh=0,tt=-1;
    for(int i=0;i<26;i++)//把第一层的节点放进队列中
        if(tr[0][i])
         q[++tt]=tr[0][i];
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=0;i<26;i++)//枚举可能的子节点
        {
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];//假如不存在这个节点,则直接跳他的上一个节点
            else
            {
                ne[p]=tr[ne[t]][i];//直接跳上一层的节点
                q[++tt]=p;//放进队列里
            }
        }
    }
}
int main()
{
   scanf("%d",&n);
   for(int i=0;i<n;i++)
   {
       scanf("%s",str);
       insert(i);//插入这个字符串
   }
   build();//求next数组
   for(int i=idx-1;i>=0;i--) f[ne[q[i]]]+=f[q[i]];//因为bfs搜索更新后的逆序就是拓扑序
   for(int i=0;i<n;i++) printf("%d\n",f[id[i]]);
    return 0;
}
相关文章
|
Go
Go 语言 errgroup 库的使用方式和实现原理
Go 语言 errgroup 库的使用方式和实现原理
359 0
|
网络协议 JavaScript 前端开发
使用正则表达式验证身份证号、QQ号、手机号、邮箱、地址、邮编、银行卡号、学号、车牌号、快递单号、验证码、ISBN号、网址、IPV4地址、IPV6地址、出生年月日、姓名2
使用正则表达式验证身份证号、QQ号、手机号、邮箱、地址、邮编、银行卡号、学号、车牌号、快递单号、验证码、ISBN号、网址、IPV4地址、IPV6地址、出生年月日、姓名
2867 0
|
监控 安全 JavaScript
Web安全-ReDos正则表达式的拒绝服务攻击
Web安全-ReDos正则表达式的拒绝服务攻击
308 2
|
SQL Rust 数据挖掘
4秒读取50w行Excel数据
4秒读取50w行Excel数据
330 1
|
数据可视化 算法 Java
了解go语言运行时工具的作用
【5月更文挑战第16天】本文简介`runtime`库提供系统调用包装、执行跟踪、内存分配统计、运行时指标和剖析支持。`internal/syscall`封装系统调用,保证uintptr参数有效。`trace`用于执行跟踪,捕获各种事件,如goroutine活动、系统调用和GC事件。`ReadMemStats`提供内存分配器统计。`metrics`接口访问运行时定义的度量,包括CPU使用、GC和内存信息。`coverage`支持代码覆盖率分析,`cgo`处理C语言交互,`pprof`提供性能剖析工具集成。这些功能帮助优化和理解Go程序的运行行为。
182 6
|
安全 搜索推荐 数据挖掘
文件解析的终极工具:Apache Tika
文件解析的终极工具:Apache Tika
1954 0
|
缓存 前端开发 JavaScript
vue-router路由使用实例详解
vue-router路由使用实例详解
266 0
|
Kubernetes 网络安全 调度
容器服务ACK常见问题之容器服务ACK的eci调度卡住如何解决
容器服务ACK(阿里云容器服务 Kubernetes 版)是阿里云提供的一种托管式Kubernetes服务,帮助用户轻松使用Kubernetes进行应用部署、管理和扩展。本汇总收集了容器服务ACK使用中的常见问题及答案,包括集群管理、应用部署、服务访问、网络配置、存储使用、安全保障等方面,旨在帮助用户快速解决使用过程中遇到的难题,提升容器管理和运维效率。
|
机器学习/深度学习 运维 搜索推荐
机器学习中准确率、精确率、召回率、误报率、漏报率、F1-Score、AP&mAP、AUC、MAE、MAPE、MSE、RMSE、R-Squared等指标的定义和说明
在机器学习和深度学习用于异常检测(Anomaly detection)、电子商务(E-commerce)、信息检索(Information retrieval, IR)等领域任务(Task)中,有很多的指标来判断机器学习和深度学习效果的好坏。这些指标有相互权衡的,有相互背向的,所以往往需要根据实际的任务和场景来选择衡量指标。本篇博文对这些指标进行一个梳理。
机器学习中准确率、精确率、召回率、误报率、漏报率、F1-Score、AP&mAP、AUC、MAE、MAPE、MSE、RMSE、R-Squared等指标的定义和说明