【AC自动机】统计单词出现个数

本文通过实战案例介绍了AC自动机的实现原理与应用技巧。基于字典树结构,详细讲解了如何构造AC自动机并进行模式匹配,适用于NOIP等编程竞赛的准备。
hz2016评测《==点

caioj.cn《==点
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define Maxchar 1000000
#define Maxm 10000
#define Maxn 50
#define Maxs 26
#define mes(x,y) memset(x,y,sizeof(x));
#define mpy(x,y) memcpy(x,y,sizeof(x))
#define INF 2147483647
using namespace std;
struct Tire{
    int s,c[Maxs+1],fail;
    Tire(){
        s=fail=0;
        mes(c,-1);
    }
}t[Maxn*Maxm+1];//字典树 
int T,tot,ans,n;
char a[Maxchar+1];
void clean(int x){
    t[x].s=t[x].fail=0;
    for(int i=1;i<=Maxs;i++)t[x].c[i]=-1;//清空字典树节点 
}
void bt(int root){
    int x=root,len=strlen(a+1);//建字典树 
    for(int i=1;i<=len;i++){//len个字符依次建节点 
        int y=a[i]-'a'+1;//把字符换成值 
        if(t[x].c[y]==-1){//如果没有节点 
            t[x].c[y]=++tot;//给新的节点一个新编号 
            clean(tot);//先给他清空再给节点 
        }
        x=t[x].c[y];//我来新建的节点继续建 
    }
    t[x].s++;//记录下再节点这个位置结尾,就是这个字符串的结尾的位置是这个节点的个数,有点啰嗦,自己意会一下。 
}
queue<int> q;
void bfs(){
    q.push(0);
    while(q.empty()==0){//bfs循环找节点 
        int x=q.front();
        for(int i=1;i<=Maxs;i++){
            int son=t[x].c[i];//在树上做kmp 
            if(son==-1)continue;//找存在的节点 
            if(x==0)t[son].fail=0;
            else{
                int j=t[x].fail;
                while(j!=0&&t[j].c[i]==-1)j=t[j].fail;//fail就是kmp的p数组然后一个一个跳 
                t[son].fail=max(t[j].c[i],0);//看看存不存在,不存在指向0,就是kmp的else p[i]=0; 
            }
            q.push(son); 
        }
        q.pop();
    }
}
void solve(){
    int x=0,len=strlen(a+1),j;
    for(int i=1;i<=len;i++){//在树上查找 
        int y=a[i]-'a'+1;
        while(x!=0&&t[x].c[y]==-1)x=t[x].fail;//再找一下和这句话有没有匹配的,有的话++ 
        x=t[x].c[y];
        if(x==-1){x=0;continue;}
        j=x;
        while(t[j].s!=0){
            ans+=t[j].s;//s就是用来记录有多少个符合的。 
            t[j].s=0;
            j=t[j].fail;
        }
    }
    printf("%d\n",ans);
}
int main(){
    scanf("%d",&T);
    while(T--){
        ans=tot=0;
        scanf("%d",&n);
        clean(0);
        for(int i=1;i<=n;i++){
            scanf("%s",a+1);
            bt(0);
        }
        bfs();
        scanf("%s",a+1);
        solve();
    }
    return 0;
}
这题还是边看代码边理解把,ac自动机是建立在Tire就是字典树的基础上,大家可以先去学一下字典树,可惜暂时没有字典树的题。 就先练一下ac自动机吧,noip可能出的(某dalao:不出我当场吃尸米) 所以大家好好背模版。  

查看原文:http://hz2016.tk/blog/?p=25
### 关于AC自动机的知识点 AC自动机是一种用于多模式串匹配的有效算法,基于Trie树和KMP算法的思想构建。它能够高效地在一个文本中查找多个模式串是否存在。其核心在于利用失败指针优化搜索过程,在某个字符不匹配时快速跳转到可能的子状态继续匹配。 #### 构建AC自动机的核心步骤 1. **建立 Trie 树**:将所有的模式串插入到一棵前缀树(Trie)中。 2. **构造失败指针**:类似于 KMP 的 next 组,通过广度优先遍历的方式为每个节点设置失败指针,指向最长可匹配的前缀。 3. **执行匹配**:在给定的文本上沿着 Trie 和失败指针移动,记录所有匹配的结果[^1]。 以下是 Python 实现的一个简单版本: ```python from collections import deque, defaultdict class ACAutomaton: def __init__(self): self.trie = {} self.fail = [] self.output = [] def insert(self, word): node = self.trie for char in word: if char not in node: node[char] = {} node = node[char] node['#'] = True # 表示单词结束 def build_fail(self): queue = deque() self.fail.append(None) # root 节点 fail 指向 None for key in self.trie.keys(): if key != '#': self.fail.append(0) queue.append((key, 1)) while queue: current_char, depth = queue.popleft() current_node = self.trie[current_char] for child_key in current_node.keys(): if child_key == '#': continue child_node = current_node[child_key] state = self.fail[depth] while state is not None and child_key not in self.trie[state]: state = self.fail[state] if state is None or child_key not in self.trie[state]: self.fail.append(0) else: self.fail.append(state + 1) queue.append((child_key, depth + 1)) def search(self, text): result = [] current_state = 0 for i, char in enumerate(text): while current_state is not None and char not in self.trie.get(current_state, {}): current_state = self.fail[current_state] if current_state is None: current_state = 0 continue current_state += 1 temp = current_state - 1 node = self.trie matched_word = "" while temp >= 0: matched_word = list(node.keys())[list(node.values()).index(temp)] + matched_word if '#' in node[temp]: result.append(matched_word) temp -= 1 node = self.trie[node[temp]] return result ``` --- ### 问题解析 问题是竞赛中的常见类型之一,通常涉及统计某些特定条件下满足约束的量。例如,“天干地支”问题可以通过模拟计算来解决年份与干支之间的映射关系。 假设我们需要编写一个程序来判断某一年对应的天干地支组合,可以按照如下方法实现: 1. 定义两个数组分别存储天干和地支。 2. 使用模运算找到对应位置上的天干和地支。 代码示例如下: ```python def year_to_gan_zhi(year): gan = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'] zhi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'] base_year = 1984 # 基准年份,该年为甲子年 offset = (year - base_year) % 60 gan_index = offset % 10 zhi_index = offset % 12 return f"{gan[gan_index]}{zhi[zhi_index]}" ``` 调用 `year_to_gan_zhi` 函即可得到任意年份对应的天干地支组合。 --- ### 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值