AC自动机专题小结

最近比较忙,AC自动机专题花了两个大周才勉强推完


关于与AC结合的一些题型如下:
1.AC自动机模板题 废话
2.AC自动机结合dp 经常会和矩阵联系起来或是一些转移的预处理(trie图),但都比较裸
3.AC自动机加fail树 个人理解是前缀树上的后缀树

模板题就不说了


Problem J

可以看出是道dp题
但是发现串的长度很大
这种题的一般思路是:
1.先敲暴力
2.矩阵快速幂优化
矩阵A[i][j]的意思是dp[k][i]对dp[k][j]的贡献
(适用于所有dp+矩阵的题

#include<cstdio>
#include<cstring>
using namespace std;
// The code is by Zerokei
#define FOR(i,x,y) for(int i=(x),i##_end_=(y);i<=i##_end_;i++)
#define DOR(i,x,y) for(int i=(x),i##_end_=(y);i>=i##_end_;i--)
#define P 100000
int Hash(char c){
    if(c=='A')return 1;
    if(c=='C')return 2;
    if(c=='T')return 3;
    return 0;
}
void Add(int &x,int y){
    x+=y;
    if(x>=P)x-=P;
}
struct Mat{
    int n,m;
    int A[105][105];
    Mat(){memset(A,0,sizeof A);}
    void Clear(){memset(A,0,sizeof A);}
    Mat operator *(const Mat &B){
        Mat res;
        res.n=n;
        FOR(i,0,n)FOR(k,0,n)
        if(A[i][k])FOR(j,0,n){
            Add(res.A[i][j],1ll*A[i][k]*B.A[k][j]%P);
        }
        return res;
    }
    void print(){
        FOR(i,0,n){
            FOR(j,0,n)printf("%d ",A[i][j]);
            puts("");
        }
        puts("");
    }
}S;
struct Aho_Corasick{
    static const int Len=105;
    int pre[Len][4],Q[Len],F[Len],fail[Len];
    int Sum[Len][Len];
    int tot,L,R;
    int newnode(){
        tot++;
        FOR(i,0,3)pre[tot][i]=0;
        fail[tot]=F[tot]=0;
        return tot;
    }
    void init(){
        tot=-1;
        newnode();
    }
    void insert(char *str){
        int len=strlen(str),cur=0;
        FOR(i,0,len-1){
            int k=Hash(str[i]);
            if(!pre[cur][k])pre[cur][k]=newnode();
            cur=pre[cur][k];
        }
        F[cur]=1;
    }
    void build(){
        L=R=0;
        FOR(i,0,3)if(pre[0][i])Q[++R]=pre[0][i];
        while(L<R){
            int k=Q[++L];
            F[k]|=F[fail[k]];
            FOR(i,0,3){
                int &p=pre[k][i];
                if(p)fail[p]=pre[fail[k]][i],Q[++R]=p;
                else p=pre[fail[k]][i];
            }
        }
    }
    void Make(){
        S.Clear();
        S.n=tot;
        FOR(i,0,tot)FOR(j,0,3){
            int k=pre[i][j];
            if(!F[k])S.A[i][k]++;
        }
    }
    void DP(int n){
        Mat ans;
        ans.n=tot;
        ans.A[0][0]=1;
        while(n){
            if(n&1)ans=ans*S;
            n>>=1;
            S=S*S;
        }
        int res=0;
        FOR(i,0,tot)Add(res,ans.A[0][i]);
        printf("%d\n",res);
    }
}ACA;
char str[15];
int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    ACA.init();
    FOR(i,1,m){
        scanf("%s",str);
        ACA.insert(str);
    }
    ACA.build();
    ACA.Make();
    ACA.DP(n);
    return 0;
} 

Problem O

trie树+fail树+线段树
fail树主要是利用它的一个dfs序,好在线段树上更新形如 [ ]+str 的串
然后在trie树上,又可以枚举形如str+[ ]的串
二者结合,就可以解决很多子串包含问题,即从自己的子串上更新一些值

#include<bits/stdc++.h>
using namespace std;
#define FOR(i,x,y) for(int i=(x),i##_END_=(y);i<=i##_END_;i++)
#define DOR(i,x,y) for(int i=(x),i##_END_=(y);i>=i##_END_;i--)
#define M 300005
#define N 20005
int Max(int x,int y){return x>y?x:y;}
void chk_mx(int &x,int y){if(x<y)x=y;}

string str[N];
int len[N],Val[N];
int n;

//dfn
int Lt[M],Rt[M],tta;

//Link
int Head[M],nxt[M],To[M],ttaE;
void addedge(int a,int b){nxt[++ttaE]=Head[a];Head[a]=ttaE;To[ttaE]=b;}
#define LFOR(i,x) for(int i=Head[x];i;i=nxt[i])

struct Segment_Tree{
    #define Lson l,mid,p<<1
    #define Rson mid+1,r,p<<1|1
    struct node{int l,r,mx;}tree[M<<2];
    void build(int l,int r,int p){
        tree[p].l=l,tree[p].r=r,tree[p].mx=0;
        if(l==r)return;
        int mid=(l+r)>>1;
        build(Lson);build(Rson);
    }
    void update(int l,int r,int sum,int p){
        if(tree[p].l==l&&tree[p].r==r){chk_mx(tree[p].mx,sum);return;}
        int mid=(tree[p].l+tree[p].r)>>1;
        if(mid>=r)update(l,r,sum,p<<1);
        else if(mid<l)update(l,r,sum,p<<1|1);
        else update(l,mid,sum,p<<1),update(mid+1,r,sum,p<<1|1);
    }
    int query(int x,int p){
        if(tree[p].l==tree[p].r)return tree[p].mx;
        int mid=(tree[p].l+tree[p].r)>>1;
        if(mid>=x)return Max(tree[p].mx,query(x,p<<1));
        else return Max(tree[p].mx,query(x,p<<1|1));
    }
}Tree;

struct Aho_Carosick{
    int Q[M],pre[M][26],fail[M];
    int L,R,tot;

    int newnode(){
        tot++;
        Head[tot]=fail[tot]=0;
        FOR(i,0,25)pre[tot][i]=0;
        return tot;
    }
    void init(){tot=-1;newnode();}

    void insert(int num){
        cin>>str[num];
        scanf("%d",&Val[num]);
        len[num]=str[num].size();
        int cur=0;
        FOR(i,0,len[num]-1){
            int k=str[num][i]-'a',&p=pre[cur][k];
            if(!p)p=newnode();
            cur=p;
        }
    }
    void build(){
        L=R=0;
        FOR(i,0,25)if(pre[0][i])Q[++R]=pre[0][i];
        while(L<R){
            int k=Q[++L];
            addedge(fail[k],k);
            FOR(i,0,25){
                int &p=pre[k][i];
                if(p)Q[++R]=p,fail[p]=pre[fail[k]][i];
                else p=pre[fail[k]][i];
            }
        }
    }
    void dfs(int x){
        Lt[x]=++tta;
        LFOR(i,x)dfs(To[i]);
        Rt[x]=tta;
    }
    void Solve(){
        Tree.build(1,tta,1);
        int ans=0;
        FOR(i,1,n){
            int cur=0,res=0;
            FOR(j,0,len[i]-1){
                int k=str[i][j]-'a';
                cur=pre[cur][k];
                chk_mx(res,Tree.query(Lt[cur],1));
            }
            Tree.update(Lt[cur],Rt[cur],res+Val[i],1);
            chk_mx(ans,res+Val[i]);
        }
        printf("%d\n",ans);
    }
}ACA;

void Init(){
    tta=ttaE=0;
    ACA.init();
}
int main(){
    int T;
    scanf("%d",&T);
    FOR(cas,1,T){
        Init();
        scanf("%d",&n);

        FOR(i,1,n)ACA.insert(i);
        ACA.build();
        ACA.dfs(0);

        printf("Case #%d: ",cas);
        ACA.Solve();
    }
    return 0;
}

Problem P

这是道返璞归真的题目233
思考点在于后缀与前缀的结合
若要查询c是否满足是前缀是a,后缀是b
其实可以这样构造
a#b c#c
然后只用判断a#b是否是c#c的子串即可

#include<bits/stdc++.h>
using namespace std;
#define FOR(i,x,y) for(int i=(x),i##_END_=(y);i<=i##_END_;i++)
#define DOR(i,x,y) for(int i=(x),i##_END_=(y);i>=i##_END_;i--)
#define M 100005
string S[M];
int pos[M];
struct Aho_Corasick{
    #define N 600005
    int pre[N][27],fail[N],Q[N],dep[N],tmp[N],pass[N];
    int tot,L,R;
    void init(){
        tot=-1;
        newnode();
    }
    int newnode(){
        tot++;
        FOR(i,0,26)pre[tot][i]=0;
        pass[tot]=fail[tot]=tmp[tot]=0;
        return tot;
    }
    void insert(int num){
        char a[N],b[N];
        string c;
        scanf("%s",a);
        scanf("%s",b);
        c+=b;c+='{';c+=a;
        int len=c.size(),cur=0;
        FOR(i,0,len-1){
            int k=c[i]-'a',&p=pre[cur][k];
            if(!p)p=newnode();
            cur=p;
            dep[p]=i;
        }
        pos[num]=cur;
    }
    void Build(){
        L=R=0;
        FOR(i,0,26)if(pre[0][i])Q[++R]=pre[0][i];
        while(L<R){
            int k=Q[++L];
            FOR(i,0,26){
                int &p=pre[k][i];
                if(p)Q[++R]=p,fail[p]=pre[fail[k]][i];
                else p=pre[fail[k]][i];
            }
        }
    }
    void Solve(string &str,int num){
        int len=str.size(),cur=0;
        FOR(i,0,len-1){
            cur=pre[cur][str[i]-'a'];
            int now=cur;
            while(now){
                if(tmp[now]==num)break;
                if((len>>1)<dep[now]);
                else pass[now]++,tmp[now]=num;
                now=fail[now];
            }
        }
    }
}ACA;
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        ACA.init();
        FOR(i,1,n){
            char s[100005];
            scanf("%s",s);
            S[i]=s;
            S[i]+='{';S[i]+=s;
        }
        FOR(i,1,m)ACA.insert(i);
        ACA.Build();
        FOR(i,1,n)ACA.Solve(S[i],i);
        FOR(i,1,m)printf("%d\n",ACA.pass[pos[i]]);
    }
    return 0;
}

还有一些小方法与小结论:
1.若一个单词对答案只有至多一次贡献,则可以直接标记一个mark
若为-1则跳过,这样的复杂度就缩为O(len)

2.若是多篇文章对于一个字典的查询,且单词对每个文章的贡献最多为1,则可以用tmp数组记录最迟贡献时间,然后在while(now)中判断即可

3.若单词对文章有多次贡献,可利用fail树,然后dfs序解,复杂度同为O(len)
但代码可能会复杂一些

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机的算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值