[BZOJ 3172] [Tjoi2013] 单词 【AC自动机】

题目链接:BZOJ - 3172

题目分析:

  题目要求求出每个单词出现的次数,如果把每个单词都在AC自动机里直接跑一遍,复杂度会很高。

  这里使用AC自动机的“副产品”——Fail树,Fail树的一个性质是,一个字符串出现的次数,就等于以它的结点为根的Fail树中的子树中所有结点的 Cnt 和。

  所以把每个单词插入的时候每个字符都 ++Cnt ,在建 Fail 的时候将结点依次压入一个栈,最后再从栈顶开始弹栈,更新栈顶元素的 Fail 的 Cnt 值,这样就是自叶子节点向上更新了。

  我开始写的时候出现的错误:建 Fail 的时候漏掉了 if (Now -> Child[i] == NULL) Now -> Child[i] = Now -> Fail -> Child[i]; 这句。这样会 RE !

代码如下:

  

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxN = 200 + 5, MaxL = 1000000 + 5, MaxC = 26;

int n, l;

char Str[MaxL];

struct Trie
{
    int Cnt;
    Trie *Fail, *Child[MaxC];
    void clear() {
        Cnt = 0;
        Fail = NULL;
        for (int i = 0; i < 26; ++i) Child[i] = NULL;
    }
} TA[MaxL], *P = TA, *Root, *Zero, *Pos[MaxN];

Trie *NewNode() {
    ++P;
    P -> clear();
    return P;
}

void AC_Init() {
    Zero = NewNode();
    Root = NewNode();
    Root -> Fail = Zero;
    for (int i = 0; i < 26; ++i) Zero -> Child[i] = Root;
}

Trie *Insert(char *Str, int l) {
    Trie *Now = Root;
    int t;
    for (int i = 0; i < l; ++i) {
        t = Str[i] - ‘a‘;
        if (Now -> Child[t] == NULL) Now -> Child[t] = NewNode();
        Now = Now -> Child[t];
        ++(Now -> Cnt);
    }
    return Now;
}

Trie *Q[MaxL], *S[MaxL];
int Head, Tail, Top;

void Build_Fail() {
    Top = 0;
    Head = Tail = 0;
    Q[++Tail] = Root;
    Trie *Now;
    while (Head < Tail) {
        Now = Q[++Head];
        S[++Top] = Now;
        for (int i = 0; i < 26; ++i) {
            if (Now -> Child[i] == NULL) Now -> Child[i] = Now -> Fail -> Child[i];
            else {
                Now -> Child[i] -> Fail = Now -> Fail -> Child[i];
                Q[++Tail] = Now -> Child[i];
            }
        }
    }
    while (Top) {
        Now = S[Top--];
        if (Now -> Fail != NULL)
            (Now -> Fail -> Cnt) += (Now -> Cnt);
    }
}

int main()
{
    scanf("%d", &n);
    AC_Init();
    for (int i = 1; i <= n; ++i) {
        scanf("%s", Str);
        l = strlen(Str);
        Pos[i] = Insert(Str, l);
    }
    Build_Fail();
    for (int i = 1; i <= n; ++i) printf("%d\n", Pos[i] -> Cnt);
    return 0;
}

  

  

时间: 2024-12-20 20:32:55

[BZOJ 3172] [Tjoi2013] 单词 【AC自动机】的相关文章

BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status][Discuss] Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6

BZOJ 3172: [Tjoi2013]单词 AC自动机

3172: [Tjoi2013]单词 Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6 Output 输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次. Sample Input 3 a aa aaa Sample Output 6 3 1 HINT 入门 #

BZOJ 3172: [Tjoi2013]单词 &&  BZOJ 2434 [Noi2011]阿狸的打字机 (施工中)

fail树 链接地址 定义 把所有fail指针逆向,这样就得到了一棵树 (因为每个节点的出度都为1,所以逆向后每个节点入度为1,所以得到的是一棵树) 还账… 有了这个东西,我们可以做很多事… 对于AC自动机的构造前面的文章已经讲了,而在查询的时候,有一点感觉没有说清楚: 对于x串在y串中出现,必然是在y串某个前缀的后缀与x串相同 fail指针指向与该节点表示串后缀相等的且长度最大的串(或前缀)的节点 然后,根据fail指针的原理,在查询的时候,沿着当前节点的fail指针向上查找,直到root结束

bzoj 3172 后缀数组|AC自动机

后缀数组或者AC自动机都可以,模板题. /************************************************************** Problem: 3172 User: BLADEVIL Language: C++ Result: Accepted Time:424 ms Memory:34260 kb ****************************************************************/ //By BLADEVI

bzoj 3172 [Tjoi2013]单词(fail树,DP)

3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 2327  Solved: 1093[Submit][Status][Discuss] Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input 第一个一个整数N,表示有多少个单词,接下来N行每行一个单词.每个单词由小写字母组成,N<=200,单词长度不超过10^6

bzoj 3172: [Tjoi2013]单词

1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #define M 1000008 5 using namespace std; 6 char ch[M]; 7 int a[M][27],n,cnt=1,fa[M],f1[M],ans[M],q[M]; 8 long long sum[M],aa; 9 void jia() 10 { 11 int now=1; 12 for(int i=1;i&

BZOJ 3172 Tjoi2013 单词 fail树

题目大意及后缀数组做法见 http://blog.csdn.net/popoqqq/article/details/41042473 原来正解是fail树--难怪后缀数组被卡成这样 首先我们将给出的n个串构建AC自动机 朴素的做法是对于每个串将这个串每个节点沿着fail指针扫一遍,将路径上的所有点的cnt++ 但是这样做会TLE 我们不妨反向思考 fail指针反向后是一棵树 沿着fail指针扫一遍就是沿着树边向根扫一遍 只在插入时将每个串的每个节点cnt++ 那么每个串终点所在fail树的子树中

BZOJ 3172 Tjoi2013 单词 后缀数组

题目大意:给定一个n个单词的文章,求每个单词在文章中的出现次数 文章长度<=10^6(不是单词长度<=10^6,不然读入直接超时) 首先将所有单词用空格连接成一个字符串,记录每个单词的起始位置和长度 然后求后缀数组,对于每个单词后缀数组中一定有连续一段后缀以这个单词开头,我们通过一开始记录的起始位置找到这个单词的后缀,然后左右端点二分答案,满足左右端点之间的后缀与原单词的LCP都当与等于原单词长度即可 时间复杂度O(nlogn) #include<cstdio> #include&

[bzoj3172][Tjoi2013]单词——AC自动机

题目大意: 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. 思路: 第i个单词在整个文章中出现了多少次即i串的结尾可以被多少个串的节点给跳到. 于是吧fail看成每个节点唯一的父亲,每个节点的权值为有多少个单词的前缀经过了它,然后直接统计子树内的权值和即可. #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++