原题连接node
算法:\(AC自动机\)ios
顾名思义,这是一道 \(AC\)自动机题目。算法
乍一看,诶,这不跟 P3796 【模板】AC自动机(增强版) 差很少吗?优化
(增强版)要求输出出现次数最多的模式串,那这个(二次增强版)直接把每一个模式串出现次数输出出来不就完了吗?ui
怎么感受还退化了呢QWQ。spa
交一发 ———— 因而成功拿到了 76 分的好成绩,其它的都 \(TLE\) 了。指针
让咱们来分析一下缘由,咱们每次是暴力跳 \(fail\), 那么若是出现这样的串呢 \(aaaaa...aaaa\),这样至关于要跳 \(len\) 次。code
究其根本,一条 \(fail\) 链上的点会被屡次修改,时间就浪费在这里了。ci
复杂度最坏状况下会退化为 \(O(|s| * |t|)\),\(|s|\) 和 \(|t|\) 均表示字符串长度。因而咱们的代码就会被卡掉。字符串
既然暴力跳 \(fail\) 会 \(T\),那么咱们如何让它很少次修改(对于每一个点只修改一次)呢?
这里介绍两种方法。
简单来讲,建 \(fail\) 指针时,更新 \(fail\) 的入度。
这样一来,在跳 \(fail\) 时,将指向当前节点的全部点都更新完以后再更新当前节点,不就能够作到只更新一次了吗。
因为我的习惯,代码中 \(S\) 是模式串,\(T\) 是文本串
完整代码
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int N = 2e6 + 10; int trie[N][27], num[N], vis[N], id[N], fail[N]; int n, tot; int match[N], ans[N], in[N]; string str, s; void insert(string s, int id){ int now = 0; int len = s.length(); for(int i = 0; i < len; i++){ int n = s[i] - 'a'; if(!trie[now][n]) trie[now][n] = ++tot; now = trie[now][n]; } match[id] = now; //标记字符串结尾位置 } void getfail(){ queue<int> q; for(int i = 0; i < 26; i++) if (trie[0][i]) q.push(trie[0][i]); while(!q.empty()){ int now = q.front(); q.pop(); for(int i = 0; i < 26; i++){ if(trie[now][i]){ fail[trie[now][i]] = trie[fail[now]][i]; in[fail[trie[now][i]]]++; //更新入度 q.push(trie[now][i]); } else trie[now][i] = trie[fail[now]][i]; } } } void query(string s){ int now = 0; for(int i = 0; i < s.size(); i++){ now = trie[now][s[i] - 'a']; vis[now]++; //只需打上标记便可 } } void topu(){ queue <int> q; for(int i = 1; i <= tot; i++) if(!in[i]) q.push(i); while(!q.empty()){ int x = q.front(); q.pop(); int y = fail[x]; in[y]--; vis[y] += vis[x]; //向上更新 if(!in[y]) q.push(y); } } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++){ cin >> str; insert(str, i); } getfail(); cin >> s; query(s); topu(); for(int i = 1; i <= n; i++) printf("%d\n", vis[match[i]]); return 0; }
建出 \(fail\) 树,直接在上面 \(dfs\),回溯的时候更新答案。
因为我的习惯,代码中 \(S\) 是模式串,\(T\) 是文本串
完整代码
#include <iostream> #include <cstring> #include <queue> #include <vector> using namespace std; const int N = 2e5 + 10; const int T = 2e6 + 10; struct node{ int v, nxt; }edge[N << 1]; int head[N], cnt; int n; char s[N] ,t[T]; int trie[N][27], tot = 0, fail[N]; int match[N], vis[N]; void add(int x, int y){ //前向星建图 edge[++cnt] = (node){y, head[x]}; head[x] = cnt; } void insert(char s[], int id){ int len = strlen(s); int now = 0; for(int i = 0; i < len; i++){ int x = s[i] - 'a'; if(!trie[now][x]) trie[now][x] = ++tot; now = trie[now][x]; } match[id] = now; //标记第i个模式串结尾位置 } //AC自动机模板 void build(){ queue <int> q; for(int i = 0; i < 26; i++) if(trie[0][i]) q.push(trie[0][i]); while(!q.empty()){ int now = q.front(); q.pop(); for(int i = 0; i < 26; i++){ if(trie[now][i]){ fail[trie[now][i]] = trie[fail[now]][i]; q.push(trie[now][i]); }else trie[now][i] = trie[fail[now]][i]; } } } void query(char s[]){ int now = 0; int len = strlen(s); for(int i = 0; i < len; i++){ now = trie[now][s[i] - 'a']; vis[now]++; //同理,只打上标记便可 } } void dfs(int x){ for(int i = head[x]; i; i = edge[i].nxt){ dfs(edge[i].v); vis[x] += vis[edge[i].v]; //更新答案 } } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++){ scanf("%s", s); insert(s, i); } scanf("%s", t); build(); query(t); for(int i = 1; i <= tot; i++) //建fail树 add(fail[i], i); dfs(0); for(int i = 1; i <= n; i++) printf("%d\n", vis[match[i]]); //输出 return 0; }