HDU 2222 Keywords Search (AC自动机)

http://acm.hdu.edu.cn/showproblem.php?pid=2222
题意:给你一些单词,然后给你一段很长的话,问你话里有多少个之前给你的那些单词(重复的只算一次)。
方法:AC自动机,即把trie树和kmp的失效匹配相结合。

AC代码如下:

#include <iostream>
#include <cstdio>
#include <string.h>
#include <queue>
using namespace std;

#define maxnode 500100
#define sigma 26 

struct ac_automation{
    int ch[maxnode][sigma];
    // maxnode 一般设置为 模式串数量*模式串长度 
    //char数组里存的都是位置信息,甚至包含了一些失效指针的信息。
    bool val[maxnode];
    int cnt[maxnode]; // count 数量
    int last[maxnode]; // 储存着最后在查询时需要向上层回溯的地址。
    int f[maxnode]; // fail指针 储存着指向的地址 
    int sz;  // the num of the trie
    int ans; // answer

    void clear(){ // 初始化
        sz = 1;
        ans = 0;
        memset(ch[0],0,sizeof(ch[0]));
        memset(val,0,sizeof(val));
        memset(cnt,0,sizeof(cnt));
    }
    int idx(char c){ 
        return c - 'a';
    }

    void insert(char s[]/*,int v*/){ // 构建trie树
        int u = 0;
        for(int i = 0; s[i];i++){
            int c = idx( s[i] );
            if(!ch[u][c]){
                memset(ch[sz],0,sizeof(ch[sz]));
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        cnt[u]++;
        val[u] = 1;  //v;
    }

    void build(){ // 构建失效指针
    //注意:构建失效匹配的时候是按照bfs的思想来的
    //就是一层一层的,即:下层的f[]和last[]是根据上层的数据来的
        f[0] = 0;
        queue<int> q;
        for(int i = 0;i < sigma;i++){
            if(ch[0][i]){
                f[ch[0][i]] = 0; // 第一层的节点的fail指针应该指向root(0)
                q.push(ch[0][i]);// 放进队列
                last[ch[0][i]] = 0;//上一层绝对是root(0)
            }
        }

        while(!q.empty()){
            int now = q.front();
            q.pop();
            for(int i = 0;i < sigma ;i++){
                int son = ch[now][i];
                if(!son){
                    ch[now][i] = ch[f[now]][i]; 
    // 提前将失效后指向此位置的的失效指针指向它应该指向的位置。 
    //因为这里找不到应有的字母,所以它应该等于自己的失效指针所指向的位置。
                    continue;
                }
                q.push(son);
                f[son] = ch[f[now]][i];
                last[son] = val[f[son]] ? f[son] : last[f[son]]; 
        /* 如果此处的fail指针指向的位置是之前插入单词的结尾,
        那么son节点的上一层(last)就为它的失效指针,
        否则去找他的失效指针的的上一层(此处一定有一个有意义的值,因为上层已经确定)*/
            }
        }
    }

    void find(char *s){
        int u = 0;
        for(int i = 0;s[i];i++){
            int c = idx(s[i]);
            u = ch[u][c];
            if(val[u]){
                print(u);
            }
            else{
                print(last[u]);
            }
        }
    }


    void print(int u){
        if(u){
            ans += cnt[u];
            cnt[u] = 0; // 因为只是找出有多少个匹配的,计入ans后需要清零,避免重复记录。
            print(last[u]);
        }
    }

}ac;

char s[60];
char text[1000100];
int main()
{
    int ncase;
    cin>>ncase;
    while(ncase--){
        int n;
        cin>>n;
        ac.clear();
        for(int i = 0;i < n; i++){
            scanf("%s",s);
            ac.insert(s);
        }
        ac.build();
        scanf("%s",text);
        ac.find(text);
        printf("%d\n",ac.ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值