算法 AC自动机

来源:互联网 发布:app软件制作教程 编辑:程序博客网 时间:2024/05/17 04:37
这篇博客拖了好久好久,真是尴尬,原因大概是我感觉很多东西,都是直觉,有种只可意会不可言传的味道,想完全搞懂,但后来仔细想想,ac自动机好像本来就不是一个非常非常确定的算法,比如说,我该拿觉很多情况可以把算法卡成n^2。所以拖了很久,酱紫。

 

先谈谈AC自动机是什么。我们知道KMP,可以快速的对一个字符串,用一个模板进行匹配。然而当我们有多个模板的时候,再去重复的使用KMP算法就显得不太合适了。所以我们找到了AC自动机。再简单的考虑一下,AC自动机可以说,是在一棵tire树上,挂上KMP的失败指针即可。

我们给出tire树部分的代码。

void insert(char *s)
{
    int lenn = strlen(s),k = 1;
    for (int i = 0;i < lenn;i++)
    {
        if (sz[k][s[i] - 'a' + 1]) k = sz[k][s[i] - 'a' + 1];else
        {
            sum++;
            k = sz[k][s[i] - 'a' + 1] = sum;
        }
    }
    gs[k]++;
}

显然这是非常非常常见的tire树写法,关于tire树,可以参考我以前的博客。 http://blog.csdn.net/qq_35772697/article/details/53444358

然后我们给出构造失败指针的代码。

void ac_match()
{
    dl.push(1);
    while (!dl.empty())
    {
        int tp = dl.front(),k;
        dl.pop();
        for (int i = 1;i <= 26;i++)
        {
            if (!sz[tp][i]) continue;
            k = pt[tp];
            while (!sz[k][i]) k = pt[k];
            pt[sz[tp][i]] = sz[k][i];
            dl.push(sz[tp][i]);
        }
    }
}


我们回忆一下KMP的算法,我们在求出第i位的失败指针的时候,只会用到,i前面的位置。所以同理,我那们在构造失败指针的时候,我们通过宽度有限搜索来进行构造,是再合适不过的了。这里我们明确一下我的数组的含义。dl,宽搜队列。sz[i][j]i号结点的下一位j字母的序号。pt[i] i 号结点的失败指针。我们来详细的说明pt[i]的含义。pt[i],指我们在i点尝试寻找他是否有儿子j的时候,如果寻找失败,我们应该去尝试pt[i]这个结点的儿子j。如果不理解,可以参考一下博客http://blog.csdn.net/qq_35772697/article/details/53442548

我们给出匹配的代码。

void solve(char *s)
{
    int lenn = strlen(s),k = 1;
    for (int i = 0;i < lenn;i++)
    {
        mark[k] = 1;
        int t = s[i] - 'a' + 1;
        while (!sz[k][t]) k = pt[k];
        k = sz[k][t];
        if (!mark[k])
        {
            for (int j = k;j;j = pt[j])
            {
                tot += gs[j];
                gs[j] = 0;
            }
        }
    }
    printf("%d\n",tot);
}

这段匹配代码是基于下面的hdu模板题而言,是求出,有多少模板串可以和目标串匹配。继续明确一下数组含义,gs[i]记录以i号结点结束的单词的个数。tot为匹配模板串的数量。mark[i]则保证了,所有可能达到匹配的位置都进行了一次考虑。

最后我觉得我应该说明关于代码的一些边界处理。比如我将0号结点的所有26个字母都设为1,避免了因为完全无法匹配无法停止while的情况。

在这里推荐一道hdu的ac自动机的模板题,hdu2222

并且在此给出AC代码。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
queue <int> dl;
int T,n,tot,sum;
bool mark[1000000];
int sz[1000000][30],gs[1000000],pt[1000000];
char tp[1200000];
void insert(char *s)
{
    int lenn = strlen(s),k = 1;
    for (int i = 0;i < lenn;i++)
    {
        if (sz[k][s[i] - 'a' + 1]) k = sz[k][s[i] - 'a' + 1];else
        {
            sum++;
            k = sz[k][s[i] - 'a' + 1] = sum;
        }
    }
    gs[k]++;
}
void ac_match()
{
    dl.push(1);
    while (!dl.empty())
    {
        int tp = dl.front(),k;
        dl.pop();
        for (int i = 1;i <= 26;i++)
        {
            if (!sz[tp][i]) continue;
            k = pt[tp];
            while (!sz[k][i]) k = pt[k];
            pt[sz[tp][i]] = sz[k][i];
            dl.push(sz[tp][i]);
        }
    }
}
void solve(char *s)
{
    int lenn = strlen(s),k = 1;
    for (int i = 0;i < lenn;i++)
    {
        mark[k] = 1;
        int t = s[i] - 'a' + 1;
        while (!sz[k][t]) k = pt[k];
        k = sz[k][t];
        if (!mark[k])
        {
            for (int j = k;j;j = pt[j])
            {
                tot += gs[j];
                gs[j] = 0;
            }
        }
    }
    printf("%d\n",tot);
}
int main()
{
    scanf("%d",&T);
    for (int mi = 1;mi <= T;mi++)
    {
        for (int i = 0;i <= sum;i++)
        {
            pt[i] = gs[i] = mark[i] = 0;
            for (int j = 1;j <= 26;j++) sz[i][j] = 0;
        }
        for (int i = 1;i <= 26;i++) sz[0][i] = 1;
        tot = 0,sum = 1;
        scanf("%d",&n);
        for (int i = 1;i <= n;i++)
        {
            scanf("%s",tp);
            insert(tp);
        }
        ac_match();
        scanf("%s",tp);
        solve(tp);
    }
    return 0;
}



0 0
原创粉丝点击