學習來源:https://oi-wiki.org/string/ac-automaton/ and 算法競賽入門經典訓練指南
題目參考代碼思路: https://www.cnblogs.com/letlifestop/p/10869285.html
題目大意:首先是T組測試樣例,然後先輸入n個字符串放在倉庫中,然後m次詢問,每一次詢問有兩種類型。類型1是在倉庫中新添加一個字符串。類型2是輸入一個字符串,然後問當前倉庫中有多少字符串是你操作2輸入的字符串的子串。
具體思路:一開始是按照在輸入新的n個字符串之後建立fail樹的,然後每次查詢的時候再重新建立一顆fail樹(因爲考慮到會添加許多新的字符串),然後就T了。既然詢問次數達到了1e5.我們就可以考慮在線的強制轉換爲離線的算法。(多謝旭偉的提醒)
我們先將所有的字符串輸入進來,然後再去建fail樹。對於每次操作2,我們從輸入的順序倒着來。在初始輸入階段中,當爲操作1時,我們保存當前字符串在trie樹上的結尾下標。當全部輸入完的
時候,倒序查詢,每查詢完一個,就將這個操作下面添加的字符串操作在trie樹上的權值下標取消就好了。
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#include <cstdio>
#include <string>
#include <stack>
#include <set>
using namespace std;
typedef long long ll;
const int N=3e6+6;
int n,m;
//離線處理結構體
struct node{
int type;
string s;
int pos;
}p[100010];
//AC自動機
namespace ac{
int tr[N][26],tot;
int val[N],fail[N],last[N];
//初始化
void init(){
for(int i=0;i<=tot;i++){
fail[i]=0;val[i]=0;last[i]=0;
for(int j=0;j<26;j++){
tr[i][j]=0;
}
}
tot=0;
}
//建立tire樹
int insert(string s){
int u=0;
for(int i=0;s[i];i++){
if(!tr[u][s[i]-'a'])tr[u][s[i]-'a']=++tot;
u=tr[u][s[i]-'a'];
}
val[u]++;
return u;//返回該字符串的節點下標
}
//建立字典圖,也就是getfail
void build(){
queue<int >q;
//初始化隊列
for(int i=0;i<26;i++){
if(tr[0][i]){
fail[tr[0][i]]=0;q.push(tr[0][i]);last[tr[0][i]]=0;
}
}
//按bfs序列計算失配函數fail和後綴鏈接last
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<26;i++){
if(tr[u][i])
fail[tr[u][i]]=tr[fail[u]][i],q.push(tr[u][i]);
else {tr[u][i]=tr[fail[u]][i];continue;}
last[tr[u][i]]=val[fail[tr[u][i]]]?fail[tr[u][i]]:last[fail[tr[u][i]]];
}
}
}
//計算以節點t結尾的所有字符串(有着相同後綴的)個數和
int cal(int t){
int res=0;
while(t){
res+=val[t];
t=last[t];
}
return res;
}
//模式匹配
int query(string t){
int u=0,res=0;
for(int i=0;t[i];i++){
int pt=t[i]-'a';
while(u&&!tr[u][pt])u=fail[u];//沿着失配邊走,知道可以匹配
u=tr[u][pt];
if(val[u]){//是模式串的結尾節點
res+=cal(u);
}
else if(last[u]){//不是模式串的結尾節點就用last[u]找到u的結尾節點(有相同後綴)
res+=cal(last[u]);
}
}
return res;
}
}
string s;
int ans[100010];
int main()
{
int t;
cin>>t;
//scanf("%d",&t);
while(t--){
ac::init();
cin>>n>>m;
//scanf("%d %d",&n,&m);
for(int i=0;i<n;i++){
cin>>s;
//scanf("%s",s);
ac::insert(s);
}
// ac::build();
int num=0;
int type;
while(m--){
cin>>type>>s;
if(type==1){
p[++num].type=type;
p[num].pos=ac::insert(s);//記錄下該字符串的節點下標,以備後續抹掉
}
else {
p[++num].type=2;
p[num].s=s;
}
}
ac::build();//建圖:得到失配函數
//倒敘查詢
for(int i=num;i>0;i--){
if(p[i].type==2){
ans[i]=ac::query(p[i].s);//模式匹配
}
else {
ac::val[p[i].pos]--;//抹掉該字符串(val是個很重要的值)
}
}
for(int i=1;i<=num;i++){
if(p[i].type==2)cout<<ans[i]<<endl;
}
}
getchar();
getchar();
return 0;
}