TJOI2013 单词 (AC自动机)

题面

在这里插入图片描述

题解

  1. AC自动机的应用,AC自动机的核心就是ne数组,表示以某节点结尾的单词的所有后缀能匹配到字典树中最长的前缀,指针指向的就是前缀的末尾端点
  1. 题中让我求的是单词在其他所有单词(包括自己)中出现的次数,如图在这里插入图片描述
    我们可以对所求问题进行转化 :求所有满足要求(后缀等于要求的单词)的前缀
  1. 那么现在就有两个问题:如何统计出所有的前缀,满足条件又改如何求。我们在建立Trie的过程,其实就是每次插入一个新的单词,那么每次插入从根节点向下,每次经过一个节点,那么以这个节点结尾的前缀就应该+1,这样就统计出了每个节点所对应的前缀。
  1. 再次回忆一下ne的作用:它能求出的是对于每一个单词的后缀所能匹配的最长的前缀,我们所要求的是对于每一个前缀,它的后缀能与哪些前缀所匹配,对比一下,ne求的是一个最长,我们要的是所有能匹配就好,那么怎么办呢,回忆一下KMP
  1. 对于KMP中 ne [i] = j ,是在1-i 区间,以i结尾所能匹配到从1开始最长的长度,那么怎么求 1-i 中 所有的前后缀重合呢,其实是一个迭代的过程,首先ne[i] 是一个,那么ne[ ne[i] ] 也是,ne [ ne[ ne[i] ] ] …一直套到开头为止,都是前后缀重合的部分
  1. 对于AC自动机也一样,我们只需要按拓扑序从后往前更新,把每次更新的结果累加,这样每个节点所对应的值就是答案,找到每个单词末尾所对应的节点编号,输出其前缀就是答案

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 1e6 + 10;

int n;
char s[N];
int tr[N][26], f[N], idx;
int ne[N], q[N];
int id[210];

void insert(int x) {
    
    
    int p = 0;
    for (int i = 0; s[i]; i++) {
    
    
        int u = s[i] - 'a';
        if (!tr[p][u]) tr[p][u] = ++idx;
        p = tr[p][u];
        f[p]++;
    }
    id[x] = p;
}

void build() {
    
    

    int hh = 0, tt = -1;
    for (int i = 0; i < 26; i++) {
    
    
        if (tr[0][i]) q[++tt] = tr[0][i];
    }

    while (hh <= tt) {
    
    
        int t = q[hh++];
        for (int i = 0; i < 26; i++) {
    
    
            int p = tr[t][i];
            if (!p) tr[t][i] = tr[ne[t]][i];
            else {
    
    
                ne[p] = tr[ne[t]][i];
                q[++tt] = p;
            }
        }
    }
}

int main() {
    
    

    cin >> n;
    for (int i = 1; i <= n; i++) {
    
    
        cin >> s;
        insert(i);
    }

    build();

    for (int i = idx - 1; i >= 0; i--) {
    
    
        f[ne[q[i]]] += f[q[i]];
    }

    for (int i = 1; i <= n; i++) {
    
    
        cout << f[id[i]] << endl;
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44791484/article/details/113800316
今日推荐