AC自动机及后缀自动机

ac自动机是一种基于trie树的算法,其本质和kmp上的处理很相似。

trie树结构:https://blog.csdn.net/qq_38890926/article/details/81158021

kmp转移思路:https://blog.csdn.net/qq_38890926/article/details/81158132

 

ac自动机组要由三个部分组成:trie树的建立  fail指针的匹配  对ac自动机的询问

每次建立自动机会有一次初始化

ac自动机类

struct node    //node结构体
{
    int exist;
    node* next[26],fail;
    node()   //初始化
    {
        exist=0;
        fail=NULL;
        memset(next,0,sizeof(next));
    }
};
struct AC
{
    node *root;
    const int SIZE=26;
    AC(){root=newnode();}
    void init(){del(root);root=newnode();} //时间换空间
    //void init(){root=newnode();}      //空间换时间
    node* newnode(){return new node;}
    //int idx(char c){return c;}     //size=128,可见字符在128以内
    inline int idx(char c){return c-'a';}     //size=26

    void insert(char *s,int num)  //插入字符串建树
    {
        node *u=root;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=idx(s[i]);
            if(u->next[c]==NULL)u->next[c]=new node();
            u=u->next[c];
        }
        u->exist=num;
        /*其他操作,在每次插入字符串的结尾node上进行操作*/
    }

    queue<node*> q; //fail指针的构建,采用bfs
    void build()
    {
        while(!q.empty())q.pop();q.push(root);
        while(!q.empty())
        {
            node* u=q.front();q.pop();//u也有u指向fail位置的性质,fail形成的集合结点有相同性质
            for(int i=0;i<SIZE;i++)
            {
                if(u->next[i]!=NULL)  //新结点,先构建fail,再加入队列
                {
                    if(u==root)u->next[i]->fail=root;//判断当前是否是根,指向根
                    else
                    {             //不是根节点往回遍历,处理和kmp相似
                        node *f=u->fail;
                        while(f!=root && f->next[i]==NULL)f=f->fail;
                        if(f->next[i]!=NULL)f=f->next[i];
                        u->next[i]->fail=f;
                    }
                     q.push(u->next[i]);
                }
            }
        }
    }

    void query(char* s)
    {
        node *u=root;
        int len=strlen(s);
        for(int i=0;i<len;i++)   //将s字符串一直向下匹配
        {
            int c=idx(s[i]);
            while(u!=root && u->next[c]==NULL)u=u->fail;
            if(u->next[c]!=NULL)
            {
                u=u->next[c];
                node* temp=u;
                while(temp!=NULL)
                {
                    if(temp->exist)
                    {
                        vec[tot]=temp->exist;
                        tot++;
                    }
                    temp=temp->fail;
                }
            }
        }
    }
    void del(node* rt)
    {
        if(rt==NULL)return;
        for(int i=0;i<SIZE;i++)if(rt->next[i]!=NULL)del(rt->next[i]);
        delete rt;
    }
};
AC ac;

数组实现

struct node    //node结构体
{
    bool exist;
    int next[55];
    int fail;
    node()   //初始化
    {
        exist=0;
        fail=0;
        memset(next,0,sizeof(next));
    }
};

struct AC
{
    int tot;
    node trie[505];
    AC(){tot=1;memset(trie,0,sizeof(trie));}
    void init(){tot=1;memset(trie,0,sizeof(trie));}
    inline int idx(char c){return mc[c];}     //size=26

    void insert(char *s)  //插入字符串建树
    {
        int u=1;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=idx(s[i]);
            if(trie[u].next[c]==0)trie[u].next[c]=++tot;
            u=trie[u].next[c];
        }
        trie[u].exist=true;
    }

    queue<int> q; //fail指针的构建,采用bfs的方式
    void build()
    {
        while(!q.empty())q.pop();
        q.push(1);
        while(!q.empty())
        {
            int u=q.front();q.pop();//u指向fail位置具有的性质u也有,即fail形成的集合结点有相同性质!!
            for(int i=0;i<SIZE;i++)
            {
                int v=trie[u].next[i];
                if(v!=0)  //对于每次存在的新结点,先构建fail,再加入队列
                {
                    if(u==1)trie[v].fail=1;  //判断当前是否是根结点,是的话下面的指向根
                    else
                    {                        //不是根节点的情况往回遍历,以下处理和kmp相似
                        int f=trie[u].fail;
                        while(f!=1 && trie[trie[f].next[i]].fail==0)f=trie[f].fail;
                        if(trie[f].next[i]!=1)f=trie[f].next[i];
                        trie[v].fail=f;
                    }
                    q.push(v);
                }
            }
        }
    }
};
AC ac;
struct node    //node结构体
{
    bool exist;
    int next[26],fail;
    node()   //初始化
    {
        exist=0;
        fail=0;
        memset(next,0,sizeof(next));
    }
};
struct AC
{
    int tot;
    node trie[505];
    AC(){init();}
    void init(){tot=1;memset(trie,0,sizeof(trie));}
    inline int idx(char c){return mp[c];} //size=26

    void insert(char *s)  //插入字符串建树
    {
        int u=0;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=idx(s[i]);
            if(trie[u].next[c]==0)trie[u].next[c]=tot++;
            u=trie[u].next[c];
        }
        trie[u].exist=true;
    }

    queue<int> q; //bfs构建fail指针
    void build()
    {
        while(!q.empty())q.pop();
        for(int i=0;i<26;i++) //先处理根
        {
            int v=trie[0].next[i];
            if(v==0)continue;
            trie[v].fail=0;
            q.push(v);
        }
        while(!q.empty())
        {
            int u=q.front();q.pop();//u指向fail位置具有的性质u也有!
            for(int i=0;i<26;i++)
            {
                int v=trie[u].next[i];
                if(v)
                {
                    trie[v].fail=trie[trie[u].fail].next[i];
                    q.push(v);
                }
                else trie[u].next[i]=trie[trie[u].fail].next[i];//就是fail
            }
        }
    }
    void query(char *s)
    {

    }
};
AC ac;

后缀自动机

 

 

应用

1.dp

在ac自动机上面进行dp,需要注意的事情:

1.由于构建fail的时候,下层的结点具有与其fail结点共有的性质,我们需要将fail节点的性质向下赋值。

2.dp的思想:将自动机上面的所有结点当作一种状态,记录处于当前状态的dp值,接下来可以进行字符串的转移,利用fail指针进行的转移来进行相关的dp。

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>

#define maxn 1005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
ll mod=998244353;
const double eps=1e-9;
using namespace std;


inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}

int n;
map<char,int> mp;
char s[maxn];
ll f[maxn][maxn];

int SIZE=4;
struct node    //node结构体
{
    bool exist;
    int next[4],fail;
    node()   //初始化
    {
        exist=0;
        fail=0;
        memset(next,0,sizeof(next));
    }
};
struct AC
{
    int tot;
    node trie[1005];
    AC(){init();}
    void init(){tot=1;memset(trie,0,sizeof(trie));}
    inline int idx(char c){return mp[c];} //size=26

    void insert(char *s)  //插入字符串建树
    {
        int u=0,len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int c=idx(s[i]);
            if(trie[u].next[c]==0)trie[u].next[c]=tot++;
            u=trie[u].next[c];
        }
        trie[u].exist=true;
    }
    queue<int> q; //bfs构建fail指针
    void build()
    {
        while(!q.empty())q.pop();
        for(int i=0;i<SIZE;i++) //先处理根
        {
            int v=trie[0].next[i];
            if(v==0)continue;
            trie[v].fail=0;
            q.push(v);
        }
        while(!q.empty())
        {
            int u=q.front();q.pop();
            if(trie[trie[u].fail].exist==true)trie[u].exist=true;//u的fail的性质u也有
            for(int i=0;i<SIZE;i++)
            {
                int v=trie[u].next[i];
                if(v)
                {
                    trie[v].fail=trie[trie[u].fail].next[i];
                    q.push(v);
                }
                else trie[u].next[i]=trie[trie[u].fail].next[i];//就是fail
            }
        }
    }
    ll query(char *str)
    {
        ll res=inf;
        int len=strlen(str);
        for(int i=0;i<=len;i++)
            for(int j=0;j<tot;j++)
                f[i][j]=inf;
        f[0][0]=0;
        for(int i=1;i<=len;i++)
        {
            for(int j=0;j<tot;j++)
            {
                for(int k=0;k<4;k++)
                {
                    int v=trie[j].next[k];
                    if(trie[v].exist==0)
                    {
                        if(mp[str[i-1]]==k)f[i][v]=min(f[i][v],f[i-1][j]);
                        else f[i][v]=min(f[i][v],f[i-1][j]+1);
                    }
                }
            }
        }
        for(int i=0;i<tot;i++)res=min(res,f[len][i]);
        if(res==inf)res=-1;
        return res;
    }
};
AC ac;


int main()
{
    mp['A'] = 0;mp['C'] = 1;mp['G'] = 2;mp['T'] = 3;
    int cas=0;
    while(scanf("%d",&n)&& n)
    {
        ac.init();
        for(int i=0;i<n;i++)
        {
            scanf("%s",s);
            ac.insert(s);
        }
        ac.build();
        scanf("%s",s);
        printf("Case %d: %lld\n",++cas,ac.query(s));
    }
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值