HDU 3065 病毒侵袭持续中(AC自动机模板)

题目大意:输出每个模板串出现的次数。

AC自动机模板题。


AC自动机个人体会:

再了解AC自动机之前,建议先看看KMP和Trie树。

 

首先看Node结构体内的各个成员所代表的含义:

ch:children缩写,这个结点后的所有儿子。如果串1:abcde的d的编号为4,另外串2:abcdf的d也会编号为4(其实abcd四个都是编号一样的),那么d的儿子有e和f;

fail:该结点失配后,应该跳去下一个匹配的结点的编号。比如abcdf的f失配后,很大可能性跳去e(得看有多少个串了);

last:表示沿着该结点的fail走,遇到的第一个单词结尾的结点。

 

这里约定好当某结点的失配指针为root时,则下一个字符将会和根节点的儿子们比较(根节点为虚拟结点);当ch[i]为-1时,表示这个儿子不存在。

 

Insert函数和Trie树中的插入是一样的。

 

然而重头戏是Build函数,构造fail指针和last指针。

 

Build函数基本方法是BFS,当前层的结点构造完之后,再检查下一层的点。

 

初始结点为根节点,把根节点加入队列。根节点的fail指针和last指针均为-1;。

 

检查队列头的所有儿子,遍历字母集,如果当前结点有为该字母的儿子,则为该儿子结点构造fail和last指针。

如果父亲就是根节点,显然儿子的fail和last都应该指向root。

 

纪录父亲的fail指针,纪录fail指针为该字母的儿子的结点编号fc(不存在则为-1);在父亲不为根节点的情况下,儿子的fail指针应该指向fc(类似于KMP的next[i+1] = k+1),而last指针则是考虑fc是不是单词结点,如果不是,指向fc的last(类似于next[i+1] = next[k+1])。

 

如果这个儿子不存在,我们也可以建一个虚拟边,把这个儿子指向fail为这个字母的儿子,这样匹配的之后,就可以自动跳转了。记得检查父亲是不是root,不然会访问下标为-1的地方(所有可能)。


search的时候,相当于匹配,而对于每一个结点,都要回溯一边last指针,找出所有以该结点为单词结尾的模板串。


#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;
#define MAXK 26
#define MAXN 50010
#define idx(c) (c - 'A')

struct Node
{
    int ch[MAXK];
    int fail, last, id;
    void Init()
    {
        memset(ch, -1, sizeof(ch));
        fail = last = -1;
        id = 0;
    }
};

struct Trie
{
    Node next[MAXN];
    int que[MAXN], head, tail;
    int cnt[1010];
    int root, sz;
    void Init()
    {
        root = sz = 0;
        next[root].Init();
        memset(cnt, 0, sizeof(cnt));
    }
    void Insert(char *s, int k)
    {
        int u = root;
        while(*s)
        {
            int c = idx(*s);
            if(next[u].ch[c] == -1)
            {
                next[++sz].Init();
                next[u].ch[c] = sz;
            }
            u = next[u].ch[c];
            s++;
        }
        next[u].id = k;
    }
    void Build()
    {
        int u, ch, f, fc;
        head = 0, tail = -1;
        que[++tail] = root;
        while(head <= tail)
        {
            u = que[head++];
            for(int i = 0; i < MAXK; i++)
            {
                f = next[u].fail;
                fc = next[f].ch[i];
                if(next[u].ch[i] != -1)
                {
                    ch = next[u].ch[i];
                    if(u == root) next[ch].fail = next[ch].last = root;
                    else
                    {
                        next[ch].fail = fc;
                        next[ch].last = (next[fc].id ? fc : next[fc].last);
                        if(next[ch].last == -1) next[ch].last = root;
                    }
                    que[++tail] = ch;
                }
                else
                {
                    if(u == root) next[u].ch[i] = root;
                    else next[u].ch[i] = next[f].ch[i];
                }
            }
        }
    }
    void Search(char *s)
    {
        int u = root;
        while(*s)
        {
            if(*s >= 'A' && *s <= 'Z') u = next[u].ch[idx(*s)];
            else u = -1;
            if(u == -1) u = root;
            int tmp = u;
            while(tmp != root)
            {
                if(next[tmp].id) cnt[next[tmp].id]++;
                tmp = next[tmp].last;
            }
            s++;
        }
    }
}ac;

char vir[1010][56];
char web[2000010];

int main()
{
//    freopen("3065.in", "r", stdin);
    int n;
    while(~scanf("%d", &n))
    {
        getchar();
        ac.Init();
        for(int i = 1; i <= n; i++)
        {
//            scanf("%s", vir[i]);
            gets(vir[i]);
            ac.Insert(vir[i], i);
        }
        ac.Build();
//        scanf("%s", web);
        gets(web);
        ac.Search(web);
        for(int i = 1; i <= n; i++) if(ac.cnt[i])
            printf("%s: %d\n", vir[i], ac.cnt[i]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值