• AC自动机学习笔记


    原博客地址:https://www.cnblogs.com/hyfhaha/p/10802604.html

    问题的背景

    来一道题:

    给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

    注意:是出现过,就是出现多次只算一次。

    img

    我们将n个模式串建成一颗字典树。但是当我们匹配成功一个模式串后,需要重新回到根做匹配,这样效率太低了。

    比如这个字典树,我们从4号点匹配失败后,可以继续从7号点开始匹配,然后匹配到8。

    那么我们怎么确定从哪个点开始匹配呢?我们称i匹配失败后从j开始匹配,j是i的失配指针。

    失配指针的实质含义是什么?

    如果一个点的Fail指针指向j,那么root到j的字符串是root到i的字符串的一个后缀。

    所以Fail指针的含义是:最长的当前字符串的后缀在字典树上可以找到的末尾编号。

    求Fail指针

    首先我们可以确定,每一个点i的Fail指针指向的点的深度一定是比i小的。

    第一层的Fail一定指的是root。

    设点i的父亲fa的Fail指针指的是Fafail,那么如果Fafail有和i值相同的儿子j,那么i的Fail就指向j。(此时我已经懵逼)

    具体实现:

    void getFail () {
        for (int i=0;i<26;i++) Trie[0].son[i]=1;
        queue<int> q;
        q.push(1);
        Trie[1].fail=0;
        while (!q.empty()) {
            int u=q.front();
            q.pop();
            for (int i=0;i<26;i++) {
                int v=Trie[u].son[i];
                int Fail=Trie[u].fail;
                if (!v) {
                    Trie[u].son[i]=Trie[Fail].son[i];
                    continue;
                }
                Trie[v].fail=Trie[Fail].son[i];
                q.push(v);
            }
        }
    }

    查询

    int query (char * s) {
        int u=1;
        int ans=0;
        int len=strlen(s);
        for (int i=0;i<len;i++) {
            int v=s[i]-'a';
            int k=Trie[u].son[v];
            while (k>1&&Trie[k].f!=-1) {
                ans+=Trie[k].f;
                Trie[k].f=-1;
                k=Trie[k].fail;
            }
            u=Trie[u].son[v];
        }
        return ans;
    }

    完整代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e5+100;
    struct trie {
        int son[26];
        int f;
        int fail;
    }Trie[maxn];
    int n,cnt;
    char s[maxn];
    queue<int> q;
    void insert (char * s) {
        int u=1;
        int len=strlen(s);
        for (int i=0;i<len;i++) {
            int v=s[i]-'a';
            if (!Trie[u].son[v]) Trie[u].son[v]=++cnt;
            u=Trie[u].son[v];
        }
        Trie[u].f++;
    } 
    void getFail () {
        for (int i=0;i<26;i++) Trie[0].son[i]=1;
        queue<int> q;
        q.push(1);
        Trie[1].fail=0;
        while (!q.empty()) {
            int u=q.front();
            q.pop();
            for (int i=0;i<26;i++) {
                int v=Trie[u].son[i];
                int Fail=Trie[u].fail;
                if (!v) {
                    Trie[u].son[i]=Trie[Fail].son[i];
                    continue;
                }
                Trie[v].fail=Trie[Fail].son[i];
                q.push(v);
            }
        }
    }
    int query (char * s) {
        int u=1;
        int ans=0;
        int len=strlen(s);
        for (int i=0;i<len;i++) {
            int v=s[i]-'a';
            int k=Trie[u].son[v];
            while (k>1&&Trie[k].f!=-1) {
                ans+=Trie[k].f;
                Trie[k].f=-1;
                k=Trie[k].fail;
            }
            u=Trie[u].son[v];
        }
        return ans;
    }
    int main () {
        cnt=1;
        cin>>n;
        for (int i=1;i<=n;i++) scanf("%s",s),insert(s);
        getFail();
        scanf("%s",s);
        printf("%d
    ",query(s));
        return 0;
    }
  • 相关阅读:
    谷歌浏览器解决跨域
    实现Linux共享Window文件
    linux安装显卡驱动
    jsduck 文档生成器
    linux 笔记
    Linux phpstorm 无法输入中文
    linux 安装composer
    Extjs动态生成表头(适用报表)
    关于git的配置与使用
    JSP解决中文乱码问题
  • 原文地址:https://www.cnblogs.com/zhanglichen/p/13209955.html
Copyright © 2020-2023  润新知