ac自动机

ac自动机有什么用呢?

一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过

学ac自动要先会trie树

我们先构建一个trie树,将ahs,shex,bcd,sha装入字典树

å¨è¿éæå¥å¾çæè¿°

我们要建立以fail数组,fail指针指向的模式串部分前缀相同,如abcebcd,我们找到c发现下一个要找的不是e,就跳到bcd中的c

这里用bfs实现,首先将深度为1的结点全部装入队列中

我们对队列中的每个节点处理

比如处理a节点,

a连接s,如果匹配s好了,但是匹配不下去了,呢么就跳到a的fail节点连接的s处,fail(as)->trie(s),然后将s节点压入队列

构建fail数组的同时,也会需要改变trie树的连接

比如我们查询ac中出现'a','c'单词的个数,呢么我们先查a,cnt++,就然后查c,字典树中没有ac,我们要直接从根结点查询出发查询c

链接:https://www.luogu.org/problem/P3808

代码:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 1000005
using namespace std;

struct AC
{
    int trie[MAXN][26];//字典树
    int cntword[MAXN];//记录单词次数
    int fail[MAXN];//回溯指针
    int cnt;

    void ins(char c[])//将单词装入字典树中
    {
        int now=0;
        int len=strlen(c);
        for(int i=0;i<len;i++)
        {
            int id=c[i]-'a';
            if(trie[now][id]==0)
                trie[now][id]=++cnt;
            now=trie[now][id];
        }
        cntword[now]++;
    }

    void getfail()
    {
        fail[0]=0;
        queue<int> que;
        for(int i=0;i<26;i++)
        {
            if(trie[0][i]){
                fail[trie[0][i]]=0;
                que.push(trie[0][i]);
            }
        }
        while(que.size())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(trie[now][i]){
                    fail[trie[now][i]]=trie[fail[now]][i];
                    que.push(trie[now][i]);
                }
                else{
                    trie[now][i]=trie[fail[now]][i];
                }
            }
        }
    }
    int query(char s[])
    {
        int now=0,ans=0;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=s[i]-'a';
            now=trie[now][c];
            for(int j=now;j&&cntword[j]!=-1;j=fail[j])
            {
                ans+=cntword[j];
                cntword[j]=-1;
            }
        }
        return ans;
    }
}AA;

char ss[MAXN];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ss+1);
        AA.ins(ss+1);
    }
    AA.getfail();
    scanf("%s",ss+1);
    printf("%d\n",AA.query(ss+1));
    return 0;
}

链接2:https://www.luogu.org/problem/P3796

查询文章中出现次数最大的单词,并按顺序输出

代码:

简单标记一下

#include<bits/stdc++.h>
#define ll long long
#define MAXN 30005
using namespace std;

struct AC
{
    int trie[MAXN][26];//字典树
    int cntword[MAXN];//记录单词次数
    int fail[MAXN];//回溯指针
    int vis[MAXN],pos[MAXN];
    int cnt;
    vector<int> vc;

    void init()
    {
        vc.clear();
        cnt=0;
        memset(vis,0,sizeof(vis));
        memset(pos,0,sizeof(pos));
        memset(cntword,0,sizeof(cntword));
        memset(fail,0,sizeof(fail));
        memset(trie,0,sizeof(trie));
    }

    void ins(char c[],int v)//将单词装入字典树中
    {
        int now=0;
        int len=strlen(c);
        for(int i=0;i<len;i++)
        {
            int id=c[i]-'a';
            if(trie[now][id]==0)
                trie[now][id]=++cnt;
            now=trie[now][id];
        }
        vc.push_back(now);
        cntword[now]++;
        pos[now]=v;
    }

    void getfail()
    {
        fail[0]=0;
        queue<int> que;
        for(int i=0;i<26;i++)
        {
            if(trie[0][i]){
                fail[trie[0][i]]=0;
                que.push(trie[0][i]);
            }
        }
        while(que.size())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(trie[now][i]){
                    fail[trie[now][i]]=trie[fail[now]][i];
                    que.push(trie[now][i]);
                }
                else{
                    trie[now][i]=trie[fail[now]][i];
                }
            }
        }
    }
    void query(char s[])
    {
        int now=0;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=s[i]-'a';
            now=trie[now][c];
            for(int j=now;j&&cntword[j]!=-1;j=fail[j])
            {
                if(cntword[j]!=0)
                    vis[j]++;
            }
        }
    }
}AA;

char cc[200][100];
char ss[1000005];
int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        AA.init();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",cc[i]+1);
            AA.ins(cc[i]+1,i);
        }
        AA.getfail();
        scanf("%s",ss+1);
        AA.query(ss+1);
        int ans=0,res;
        for(int i=0;i<AA.vc.size();i++)
        {
            int v=AA.vc[i];
            ans=max(ans,AA.vis[v]);
        }
        printf("%d\n",ans);
        for(int i=0;i<AA.vc.size();i++)
        {
            int v=AA.vc[i];
            if(AA.vis[v]==ans){
                printf("%s\n",cc[AA.pos[v]]+1);
            }
        }
    }
    return 0;
}

https://vjudge.net/problem/POJ-2778#author=lypl

题意:

有m种DNA序列是致病的,问长为n且不包含致病序列的DNA有多少种 

解析:

ac自动机+矩阵快速幂

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define mod 100000
#define ll long long
#define MAXN 10005
#define N 101
using namespace std;
int idx[200];
int trie[MAXN][5];
int fail[MAXN];
int vis[MAXN];
int cnt,sz;

void init()
{
    memset(trie,0,sizeof(trie));
    memset(fail,0,sizeof(fail));
    memset(vis,0,sizeof(vis));
    cnt=0;
}

void ins(char c[])
{
    int now=0;
    int len=strlen(c);
    for(int i=0;i<len;i++)
    {
        int id=idx[c[i]];
        if(trie[now][id]==0)
            trie[now][id]=++cnt;
        now=trie[now][id];
    }
    vis[now]=1;
}

void getfail()
{
    fail[0]=0;
    queue<int> que;
    for(int i=1;i<=4;i++){
        if(trie[0][i])
            que.push(trie[0][i]);
    }
    while(que.size())
    {
        int now=que.front();
        que.pop();
        for(int i=1;i<=4;i++)
        {
            if(trie[now][i]){
                fail[trie[now][i]]=trie[fail[now]][i];
                que.push(trie[now][i]);
            }
            else{
                trie[now][i]=trie[fail[now]][i];
            }
            vis[trie[now][i]]|=vis[trie[fail[now]][i]];
        }
    }
}

struct matrix
{
    ll a[N+1][N+1];
    matrix(){
        memset(a,0,sizeof(a));
    }
    friend matrix operator*(matrix A,matrix B)
    {
        matrix C;
        for(int i=0;i<sz;i++){
            for(int j=0;j<sz;j++){
                for(int k=0;k<sz;k++){
                    C.a[i][j]=(C.a[i][j]+A.a[i][k]*B.a[k][j])%mod;
                }
            }
        }
        return C;
    }
};

matrix qpow(matrix A,ll m)//矩阵A的m次幂
{
    matrix ans;//单位矩阵
    for(int i=0;i<sz;i++)
        ans.a[i][i]=1;
    while(m)
    {
        if(m&1) ans=ans*A;
        A=A*A;
        m>>=1;
    }
    return ans;
}

int main()
{
    idx['A']=1;idx['T']=2;idx['C']=3;idx['G']=4;
    int n,m;
    char ss[11];
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        init();
        for(int i=1;i<=m;i++)
            scanf("%s",ss),ins(ss);
        sz=cnt+1;
        getfail();
        matrix A;
        for(int i=0;i<=cnt;i++)
        {
            if(vis[i])
                continue;
            for(int j=1;j<=4;j++){
                if(vis[trie[i][j]])
                    continue;
                A.a[i][trie[i][j]]++;
            }
        }
        A=qpow(A,n);//A矩阵n次
        ll ans=0;
        for(int i=0;i<=cnt;i++)
        {
            ans+=A.a[0][i];
            ans=ans%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值