P3966 [TJOI2013]单词(AC自动机,Trie图)

本文介绍了如何使用AC自动机(Aho-Corasick Automaton)在论文中高效统计特定单词出现次数,通过Trie树实现并结合KMP算法的失败指针构建,简化了匹配过程。通过拓扑排序,自底向上更新答案,从而快速得到每个单词的出现次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

初学ac自动机。
ac自动机相当于在trie上建立kmp中的ne指针(fail树)

建树的函数build()比较固定;利用bfs+queue的特点还可以同时求出trie的拓扑序。

匹配的时候就可以灵活操作了。。

题意

某人读论文,一篇论文是由许多单词组成的。

但他发现一个单词会在论文中出现很多次,现在他想知道每个单词分别在论文中出现多少次。

输入格式
第一行一个整数 N,表示有多少个单词。

接下来 N 行每行一个单词,单词中只包含小写字母。

输出格式
输出 N 个整数,每个整数占一行,第 i 行的数字表示第 i 个单词在文章中出现了多少次。

数据范围
1≤N≤200,
所有单词长度的总和不超过 106。

输入样例:
3
a
aa
aaa
输出样例:
6
3
1

分析

先建立自动机,发现只需要统计每个单词能被多少个节点跳到就行了。

举例: s=“abcd”; t=“abcdeabcde”
在trie树上:
t[3]会指向s[3](也就是自己,s[3]在trie树上与t[3]是同一个位置,统计为cnt[s[3]]=2)
t[8]会指向s[3](在trie树上与t[3]是同一个位置)
所以,s[3]这个位置的cnt初值为2,又被一个节点指向了->ans=3。
解释的很抽象,,

于是:将所有单词建立trie图之后,利用顺便求出来的拓扑序自底向上更新每个节点的答案值就行了。

代码

#include <bits/stdc++.h>

using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)

//---------------
const int N = 1e6 + 10;
int n;
int tr[N][26], cnt[N], ne[N], idx;
char str[N];
int id[210];
int q[N];
void init()
{
    memset(tr, 0, sizeof tr);
    memset(cnt, 0, sizeof cnt);
    memset(ne, 0, sizeof ne);
    idx = 0;
}

void insert(int x)
{
    int root = 0;
    for (int i = 0; str[i]; i++)
    {
        int now = str[i] - 'a';
        if (!tr[root][now])
            tr[root][now] = ++idx;
        root = tr[root][now];
        cnt[root]++;
    }
    id[x] = root;
}

void ne_build()
{
    int hh = 0, tt = -1;
    fir(i, 0, 25) if (tr[0][i]) q[++tt] = tr[0][i];
    while (hh <= tt)
    {
        auto t = q[hh++];
        fir(i, 0, 25)
        {
            int now = tr[t][i];
            if (now)
            {
                ne[now] = tr[ne[t]][i];
                q[++tt] = now;
            }
            else
            {
                tr[t][i] = tr[ne[t]][i];
            }
        }
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int StartTime = clock();
#endif
    scanf("%d", &n);
    fir(i, 1, n)
    {
        scanf("%s", str);
        insert(i);
    }
    ne_build(); //还可以顺便做topsort
    rif(i, idx - 1, 0)//queue中一共存过idx个点,编号从0开始 所以是0~idx-1。
    {
        cnt[ne[q[i]]] += cnt[q[i]];
    }
    fir(i, 1, n) printf("%d\n", cnt[id[i]]);
#ifndef ONLINE_JUDGE
    printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值