【洛谷】3966:[TJOI2013]单词【AC自动机】【fail树】
题目描述
小张最近在忙毕设,所以一直在读论文。一篇论文是由许多单词组成但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现了多少次。
输入输出格式
输入格式:
第一行一个整数N,表示有N个单词。接下来N行每行一个单词,每个单词都由小写字母(a-z)组成。(N≤200)
输出格式:
输出N个整数,第i行的数表示第i个单词在文章中出现了多少次。
输入输出样例
说明
数据范围
30%的数据, 单词总长度不超过10^3
100%的数据,单词总长度不超过10^6
Solution
解法也是挺简单的,首先肯定是建出ac自动机。
然后我们考虑fail指针的含义,$fail[u]$到根节点是$u$到根节点串的后缀,也就是以$fail[u]$为结尾的串在$u$中出现了$cnt[u]$次...
所有的fail都是这样的,所以反向建fail指针变成fail树,统计子树和更新$cnt$,$cnt[u]$就是每个$u$为结尾的串出现的次数了。
在bfs的时候存下遍历顺序,最后倒着更新就可以了。
Code
#include<bits/stdc++.h> #define LL long long using namespace std; string s[1000005]; int n; LL ans[1000005], cnt[1000005]; int son[1000005][27], fail[1000005], tail, las[1000005]; void insert(string a, int opt) { int len = a.length(); int nd = 0; for(int i = 0; i < len; i ++) { int t = a[i] - 'a' + 1; if(!son[nd][t]) son[nd][t] = ++ tail; nd = son[nd][t]; cnt[nd] ++; } las[opt] = nd; } int Q[1000005], t; void get_fail() { queue < int > q; Q[++t] = 0; for(int i = 1; i <= 26; i ++) if(son[0][i]) fail[son[0][i]] = 0, q.push(son[0][i]); while(!q.empty()) { int u = q.front(); q.pop(); for(int i = 1; i <= 26; i ++) { if(son[u][i]) fail[son[u][i]] = son[fail[u]][i], q.push(son[u][i]), Q[++t] = son[u][i]; else son[u][i] = son[fail[u]][i]; } } } int main() { scanf("%d", &n); for(int i = 1; i <= n; i ++) { cin >> s[i]; insert(s[i], i); } get_fail(); for(int i = t; i; i --) { int now = Q[i]; cnt[fail[now]] += cnt[now]; } for(int i = 1; i <= n; i ++) printf("%lld\n", cnt[las[i]]); return 0; }