zoukankan      html  css  js  c++  java
  • HDU

    链接:HDU-2222

    很久之前就准备学AC自动机了,今天就把最基础的搞定一下。

    由于是最基础的AC自动机所以我先总结一下基础内容 ( qvq

    最基础的AC自动机解决的是:多字符串匹配问题,我们知道kmp是优化 一对一的匹配。那么 N对一的匹配难道要用kmp跑 N次?答案是no.

    AC自动机全称:Aho-Corasick automaton , 既然有这个算法,那肯定对 这个问题就有其更加优化的解决方案。

    网上都说AC自动机是 KMP x 字典树(hush表好像也能存)。字典树当然是肯定的,因为要建一台AC自动机第一步就是要用已有的模式串构建出字典树。对于KMP部分就主要是对 fail 这个数组的理解。

    我们知道KMP中的next数组是求 当前长为 i 的串的最长前后缀长度。由于存储了这个,我们在失配的时候就可以跳过相同的部分直接匹配之后失配的位置

    AC自动机也是同理,其有一个fail数组存储的是 与该节点 最长公共后缀的节点位置。比如 模式串 her she 与文本串 sher , 我们知道 she的h指向her的h , she的e会顺着其父节点的位置指到 her的e

    所以文本串sher 遍历完she之后(说明she已经存在)就跳到her的e位置继续匹配 ,最后发现 最后位r 刚好匹配,所以 文本串中就出现了 her ,she 两个模式串

    由于我们不能漏掉任意一个串,所以每遇到有fail指针的位置我们就要跳,这样尽可能把所有的串都找完。 建树的时候我们还要用一个val数组统计 以该结点结束的个数,这样最后查询到后加上这个值就可以统计个数。但是下面代码中val数组被使用后就被置为-1(已经使用过),就会出现一个问题,这台AC自动机不能持久的跑,只能跑一次。所以之后就会有持久化等各种优化。

    由于这里是最基础的AC自动机所以就不管了...

    #include<bits/stdc++.h>
    const int N=1000000+5;
    using namespace std;
    int tot;//编号
    int trie[N][26];//字典树
    int val[N];//字符串结尾标记(以当前前缀结尾的字符串个数)
    int fail[N];//失配指针
    void insert(char *s){//插入模式串
        int root=0;//字典树上当前匹配到的结点
        for(int i=0;s[i];i++){
            int id=s[i]-'a';//子节点编号
            if(trie[root][id]==0)//若之前没有从root到id的前缀
                trie[root][id]=++tot;//插入
            root=trie[root][id];//顺着字典树往下走
        }
        val[root]++;
    }
    void build(){//构建fail指针域建立字典图
        queue<int>q;
        for(int i=0;i<26;i++)//将根节点的子节点入队
            if(trie[0][i])
                q.push(trie[0][i]);
      
        while(!q.empty()){
            int k=q.front();//对于队首节点k,其fail指针已求得,现在要求的是他子节点的fail指针
            q.pop();
            for(int i=0;i<26;i++){//遍历字符集
                if(trie[k][i]){//若字符i对应的子节点存在
                    fail[trie[k][i]]=trie[fail[k]][i];//将这个子节点fail指针赋给fail[k]的字符i对应的节点
                    q.push(trie[k][i]);
                }
                else
                    trie[k][i]=trie[fail[k]][i];//将fail[k]的子节点直接赋成k的子节点
            }
        }
    }
    int query(char *t){//对文本串进行匹配
        int res=0;//存储结果
        int root=0;//字典树上当前匹配到的结点
        for(int i=0;t[i];i++){//对文本串进行遍历
            int id=t[i]-'a';//子节点编号
            root=trie[root][id];//在字典图中不断穿梭跳动
            int j=root;
            while(j&&val[j]!=-1){//利用fail指针找出所有匹配的模式串
                res+=val[j];//累加到答案中
                val[j]=-1;//表示已经添加(不会重复添加)
                j=fail[j];//fail指针跳转
            }
        }
        return res;
    }
    char P[N];
    char T[N];
    int main(){
        int t;
        scanf("%d",&t);
        while(t--){
            memset(trie,0,sizeof(trie));
            memset(val,0,sizeof(val));
            memset(fail,0,sizeof(fail));
            tot=0;
     
            int n;//模式串个数
            scanf("%d",&n);
            while(n--){
                scanf("%s",P);//输入模式串
                insert(P);//插入字典树中
            }
            build();//构建失配指针与字典图
     
            scanf("%s",T);//输入文本串
            int res=query(T);
            printf("%d
    ",res);
        }
        return 0;
    }
  • 相关阅读:
    学习WWDC的好资源!
    运行 CMD 时,參数加引號常见问题
    FileChannel的深入理解
    C#单例模式的三种写法
    Linux 安装Nginx具体图解教程
    计网面试题
    VS:&quot;64位调试操作花费的时间比预期要长&quot;的一解决途径
    中小型WEB系统权限日志数据表设计
    CDN服务上线,DNSPOD布局云端生态圈
    怎样利用ash监控会话
  • 原文地址:https://www.cnblogs.com/Tianwell/p/11373509.html
Copyright © 2011-2022 走看看