题目链接:HDU-2222 或 Vjudge
简单说明:
ac自动机的建立,其中插入过程借助了字典树,处理回溯数组(也有人称失败数组)过程是一个广搜运用了STL的队列(queue)。ac自动机的过程还在写。
这是学习ac自动机的第一题,如果wa的话,那就要注意,字串结束标记是如何标记的了。它不能单单 记录,还要 计数。WA了好几次……
my codes:
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <queue> #define set(a) memset(a,0,sizeof(a)) #define set_b(a) memset(a,false,sizeof(a)) using namespace std; const int M=5e5+10; const int N=1e6+10; int tree[M][26];/**< tree[u][i] 表示节点u的i儿子的节点值 */ int ed[M];/**< 字串结束标记,运用比较灵活,本题要计数 */ int nxt[M];/**< 回溯数组 */ char s[N];/**< 文本串 */ int tot;/**< 节点数 */ struct ac_auto { void pre_set()/**< 初始化 */ { tot=1; set(ed); set(tree); set(nxt); } void build(char str[])/**< 基本插入操作 */ { int len=strlen(str); int u=1; for(int i=0; i<len; i++) { int v=str[i]-‘a‘; if(!tree[u][v]) tree[u][v]=++tot; u=tree[u][v]; } // printf("%d\n",u); ed[u]++; } void deal_next()/**< 处理回溯数组,用了一个虚拟的零号节点,下面两行表示它的所有儿子与一号建边 */ { for(int i=0; i<26; i++) tree[0][i]=1; queue<int> q; while(!q.empty()) q.pop(); nxt[1]=0;/**< 一号节点回溯到零号 */ q.push(1);/**< 用到了零号节点,先压入 1 。如果不设零号的话,是压入 1 的所有儿子。两种,注意区别 */ while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0; i<=25; i++) { if(!tree[u][i])/**< u没有i这个儿子 */ tree[u][i]=tree[nxt[u]][i]; /** \brief u 与它的i 儿子之间建边。有了这句话那么每个节点都将 有26个儿子,就不会存在 某节点没有 i 儿子还要继续找 父亲的回溯节点的信息这一麻烦。 \return */ else { nxt[tree[u][i]]=tree[nxt[u]][i];/**< u的i儿子nxt值是next[u]的i儿子的号码 */ // printf("nxt[%d]=%d\n",tree[u][i],tree[nxt[u]][i]); q.push(tree[u][i]); } // printf("%d 的 %c 儿子编号:%d\n",u,‘a‘+i,tree[u][i]); } } } int search() { int len=strlen(s); int ans=0; int u=1,v; for(int i=0; i<len; i++) { v=s[i]-‘a‘; u=tree[u][v]; int tp=u; while(tp>1&&ed[tp]) { ans+=ed[tp]; ed[tp]=0; tp=nxt[tp]; } } return ans; } }; int main() { int T,n; cin>>T; while(T--) { ac_auto().pre_set(); cin>>n; while(n--) { char str[55]; scanf("%s",str); ac_auto().build(str); } scanf("%s",s); ac_auto().deal_next(); int ans=ac_auto().search(); cout<<ans<<endl; } return 0; }
原文地址:https://www.cnblogs.com/Ycourage/p/9371230.html
时间: 2024-10-11 23:45:11