洛谷 P5357 【模板】AC自动机(二次加强版)
洛谷 P5357 【模板】AC自动机(二次加强版)
Solution
算法:\(AC自动机\)
顾名思义,这是一道 \(AC\)自动机题目。
乍一看,诶,这不跟 P3796 【模板】AC自动机(加强版) 差不多吗?
(加强版)要求输出出现次数最多的模式串,那这个(二次加强版)直接把每个模式串出现次数输出出来不就完了吗?
怎么感觉还退化了呢QWQ。
交一发 ———— 于是成功拿到了 76 分的好成绩,其它的都 \(TLE\) 了。
让我们来分析一下原因,我们每次是暴力跳 \(fail\), 那么如果出现这样的串呢 \(aaaaa...aaaa\),这样相当于要跳 \(len\) 次。
究其根本,一条 \(fail\) 链上的点会被多次修改,时间就浪费在这里了。
复杂度最坏情况下会退化为 \(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 树
建出 \(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;
}