码迷,mamicode.com
首页 > 其他好文 > 详细

AC自动机

时间:2018-08-30 01:56:34      阅读:172      评论:0      收藏:0      [点我收藏+]

标签:ace   前缀   target   自动   没有   入队   16px   ret   include   

传送门1(简单版)

传送门2(增强版)

AC自动机……(至于自动机是啥我也看不懂……请自行百度)

AC自动机简单来说可以被看成是trie树和KMP算法的结合体,它的用途主要是多模匹配,就是给你一个文本串和多个模式串,询问你诸如:有多少个模式串在文本串中出现过,或是什么模式串在文本串中出现了多少次之类的。

AC自动机的重点在于fail数组的构造。fail数组简单来说可以指当前字符串的一个后缀在这个trie树中所对应的最长的,且能与该后缀匹配的前缀的末尾位置。是不是很像KMP?我们说一下怎么构造fail数组。

首先按照构造trie树的方法构造一下模式串的trie树。这个应该不用说了……

构造fail数组的方法是bfs。首先,对于根结点上所连的点,把他们的fail全都设为root(就是0),并且把他们压入队列。之后每次取队列首元素,对于其子节点(v),首先跳转到当前节点的fail数组的位置(k),找这个节点(k)有没有与v相同的子节点(u),如果有,那么v的fail就是u,否则的话就再找k的fail,一直到找到或者回到root为止。

看样子写起来会很长……不过有这样一份代码。

void getfail()
    {
    rep(i,0,25) if(c[0][i]) fail[c[0][i]] = 0,q.push(c[0][i]);
    while(!q.empty())
    {
        int k = q.front();q.pop();
        rep(i,0,25)
        {
        if(c[k][i]) fail[c[k][i]] = c[fail[k]][i],q.push(c[k][i]);
        else c[k][i] = c[fail[k]][i];
        }
    }
    }

这个是不是非常短呀?

重点在于这一句:c[k][i] = c[fail[k]][i];像是并查集一样,这样我们在求fail的时候只要访问一次fail就可以知道它的fail应该是什么了。

因为家里电脑很爆炸……图先不画了……明天补上。

这样就非常容易的把fail数组建好了。

之后就是匹配了。匹配其实很简单,只要在每次匹配的时候都向其fail跳一次看看能否匹配即可。注意要先符合条件。在每次匹配上的时候,我们把匹配数目++。

这样简单版的就做好了……图明天补上。因为有上面类似并查集的操作,所以整个trie树变成了一个trie图,使得它即使匹配到末尾仍然能匹配回来。

增强版其实也并不难。我们只要在每次匹配的时候把匹配次数++,并且记录是哪个字符串匹配了即可。注意这里不能像简单版一样把一个节点的权值赋成-1,因为将来你还有可能再用,比如说在出现相同的字符串的时候。(这个在luogu上卡掉了我第一个点……图明天补上)

这样就可以了,看一下代码。

简单版:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using namespace std;
const int N = 500005;
const int M = 1000005;
typedef long long ll;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -) op = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        ans *= 10;
        ans += ch - 0;
        ch = getchar();
    }
    return ans * op;
}

queue<int> q;

struct ACG
{
    int c[N][26],val[N],fail[N],cnt;
    void ins(char *s)
    {
        int len = strlen(s);
        int now = 0;
        rep(i,0,len-1)
        {
            int v = s[i] - a;
            if(!c[now][v]) c[now][v] = ++cnt;
            now = c[now][v];
        }
        val[now]++;
    }
    
    void getfail()
    {
        rep(i,0,25) if(c[0][i]) fail[c[0][i]] = 0,q.push(c[0][i]);//对根的儿子求fail 
        while(!q.empty())//bfs求fail 
        {
            int k = q.front(); q.pop();
            rep(i,0,25)
            {    
                if(c[k][i]) fail[c[k][i]] = c[fail[k]][i],q.push(c[k][i]);
                else c[k][i] = c[fail[k]][i];
            }
        }
    }
    
    int query(char *s)
    {
        int len = strlen(s);
        int now = 0,ans = 0;
        rep(i,0,len-1)
        {
            now = c[now][s[i]-a];
            for(int t = now; t && val[t] != -1; t = fail[t])
            ans += val[t],val[t] = -1;
        }
        return ans;
    }
}AC;

int n;
char p[M];

int main()
{
    n = read();
    rep(i,1,n) scanf("%s",p),AC.ins(p);
    AC.getfail();
    scanf("%s",p);
    int ans = AC.query(p);
    printf("%d\n",ans);
    return 0;
}

增强版:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using namespace std;
const int N = 500005;
const int M = 1000005;
typedef long long ll;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
    if(ch == -) op = -1;
    ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
    ans *= 10;
    ans += ch - 0;
    ch = getchar();
    }
    return ans * op;
}

queue<int> q;
int t,sum[200],maxn;
char str[M],s[200][200];
struct ACG
{
    int c[N][26],p[N],fail[N],cnt,d[N];
    void clear()
    {
    memset(c,0,sizeof(c));
    memset(fail,0,sizeof(fail));
    memset(d,0,sizeof(d));
    memset(p,0,sizeof(p));
    cnt = 0;
    }
    void insert(char *s,int f)
    {
    int len = strlen(s),now = 0;
    rep(i,0,len-1)
    {
        int v = s[i] - a;
        if(!c[now][v]) c[now][v] = ++cnt;
        now = c[now][v];
    }
    p[now]++,d[now] = f;
    }
    void getfail()
    {
    rep(i,0,25) if(c[0][i]) fail[c[0][i]] = 0,q.push(c[0][i]);
    while(!q.empty())
    {
        int k = q.front();q.pop();
        rep(i,0,25)
        {
        if(c[k][i]) fail[c[k][i]] = c[fail[k]][i],q.push(c[k][i]);
        else c[k][i] = c[fail[k]][i];
        }
    }
    }
    void match(char *s)
    {
    int len = strlen(s),now = 0;
    rep(i,0,len-1)
    {
        now = c[now][s[i] - a];
        for(int g = now;g && p[g] != -1;g = fail[g])
        {
        if(p[g]) sum[d[g]]++;
        //else p[g] = -1;
        }
    }
    }
}AC;
int main()
{
    while(1)
    {
    t = read(); if(!t) break;
    AC.clear(),memset(sum,0,sizeof(sum)),maxn = 0;
    rep(i,1,t) scanf("%s",s[i]),AC.insert(s[i],i);
    AC.getfail();
    scanf("%s",str),AC.match(str);
    rep(i,1,t) maxn = max(maxn,sum[i]);
    printf("%d\n",maxn);
    rep(i,1,t) if(sum[i] == maxn) printf("%s\n",s[i]);
    }
    return 0;
}

 

AC自动机

标签:ace   前缀   target   自动   没有   入队   16px   ret   include   

原文地址:https://www.cnblogs.com/captain1/p/9557709.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!