Evanyou Blog 彩带

P3808 【模板】AC自动机(简单版)

题目背景

这是一道简单的AC自动机模板题。

用于检测正确性以及算法常数。

为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。

管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意

题目描述

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

输入输出格式

输入格式:

第一行一个n,表示模式串个数;

下面n行每行一个模式串;

下面一行一个文本串。

输出格式:

一个数表示答案

输入输出样例

输入样例#1: 
2
a
aa
aa
输出样例#1: 
2

说明

subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;

subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;

 

Solution:

  AC自动机板子。

  简单讲一下,对于多模式串的匹配,如果都用kmp解决则主串要扫多次,复杂度直接变为$O(n^2)$。

  而AC自动机可以只扫一次主串进行多模式串匹配,先将所有模式串构建出一棵trie树,然后类比kmp我们在trie树上构建失配边,每次失配直接跳向失配边所指指针继续匹配,这样就能将匹配的时间复杂度变为$O(\sum{|P|}+|T|)$,至于求失配边我们可以类比kmp的next数组求法,将递归改为在trie树上bfs递推,递推很好理解直接见代码。

  解释一下fail数组:根节点到$fail[i]$表示的字符串,是根节点到$i$表示的字符串的最长后缀。

  这样就能保证到了$i$节点时,根节点到$fail[i]$所表示的字符串一定出现过,失配时就能接上去继续匹配。

  小技巧:

  1、在AC自动机的构建时,往往会有一个类似路径压缩的优化:当不存在$trie[p][i]$节点时,直接$trie[p][i]=trie[fail[p]][i]$,即将该空节点指向其失配边所指节点的该字符节点。

  2、匹配时往往会多次走失配边,影响效率,于是另起一个$last[i]$表示$i$节点所指向的失配边下一个出现的字符串结尾(专业术语叫后缀链接——suffix link),实现时直接在bfs求解fail数组时递推就好了。

  对于本题,我们直接建好AC自动机,然后愉快的匹配,每次都累加一下失配边所指的完整字符串个数就好了,注意一下每个模式串重复出现只能算一次,所以累加完后要清除end标记。然后的话本题数据比较水,last数组优化并不明显,但是在加强版的模板题中优化效果还是可以的。(话说AC自动机并不难,以前咋不会呢?用下心体会,就好了!)

代码:

 

#include<bits/stdc++.h>
#define il inline
#define ll long long
#define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(int (i)=(b);(i)>=(a);(i)--)
using namespace std;
const int N=500005;
int n,trie[N][26],tot,end[N],fail[N],last[N];
char s[N<<1];

il void insert(char *s){
    int len=strlen(s)-1,p=0,x;
    For(i,0,len){
        x=s[i]-'a';
        if(!trie[p][x])trie[p][x]=++tot;
        p=trie[p][x];
    }
    end[p]++;
}

il void bfs(){
    queue<int>q;
    For(i,0,25) if(trie[0][i]) fail[trie[0][i]]=0,q.push(trie[0][i]);
    while(!q.empty()){
        int u=q.front();q.pop();
        For(i,0,25){
            int v=trie[u][i];
            if(v) fail[v]=trie[fail[u]][i],last[v]=end[fail[v]]?fail[v]:last[fail[v]],q.push(v);
            else trie[u][i]=trie[fail[u]][i];
        }
    }
}

il int find(char *s){
    int ans=0,len=strlen(s)-1,p=0;
    For(i,0,len){
        p=trie[p][s[i]-'a'];
        for(int j=p;j&&end[j]!=-1;j=last[j]) ans+=end[j],end[j]=-1;
    }
    return ans;
}

int main(){
    scanf("%d",&n);
    For(i,1,n) scanf("%s",s),insert(s);
    bfs();
    scanf("%s",s);
    cout<<find(s);
    return 0;
}

 

 

 

 

 

posted @ 2018-08-08 16:27  five20  阅读(148)  评论(0编辑  收藏  举报
Live2D