洛谷 P5357 【模板】AC自動機(二次加強版)
阿新 • • 發佈:2021-08-07
洛谷 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;
}