题目链接:https://ac.nowcoder.com/acm/problem/14612
题意:
维护一个在线 AC 自动机,要求实时添加和查询模式串。查询和添加次数 <= 10^5,字符串总长度 <= 3*10^6。
题解:
设 n 为模式串个数,l 为模式串平均长度。
AC 自动机不支持实时添加模式串,但可以通过维护多个 AC 自动机来达到同样的效果。
有两种思路,第一种是维护两个自动机,往第一个里添加模式串,如果模式串的数量超过一个阈值就把第一个自动机合并到第二个自动机里。如果阈值是 sqrt(n) 左右,那每次添加模式串的复杂度均摊是 O(sqrt(n)*l) 的,查询的复杂度是 O(l) 的。
第二种思路是维护一个自动机队列,每一次为模式串新创建一个自动机加入这个队列,每当队列最后一个自动机的大小比倒数第二个自动机的二分之一大,就合并这两个自动机。这样,每次添加和查询的均摊复杂度都是 O(log(n)*l) 的。
第一种思路的运行时间约为第二种的四倍,下面提供第二种思路的实现代码。
合并时注意保留节点是否为字符串终结点的信息
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5,maxm=3e6+5,cut=300; //maxn: µ¥¸ö×Ö·û´®×³¤¶È maxm: ac×Ô¶¯»ú×î¶à½ÚµãÊý
struct node{
node *fa,*ch[26],*fail;
int end,cnt; //cnt: Åܵ½Õâ¸ö½ÚµãÉÏʱ£¬×Ô¶¯»úÄÚÆ¥Åä³É¹¦µÄ×Ö·û´®ÊýÁ¿
node(){
fa=fail=NULL; end=cnt=0;
fill(ch,ch+26,fa);
}
inline void set_fail(node *u){
fail=u;
cnt=end+(u->cnt);
}
};
node *merge_node(node *a,node *b){
if (!a) return b;
if (!b) return a;
int i;
node *v;
for (i=0;i<26;i++){
v=a->ch[i]=merge_node(a->ch[i],b->ch[i]);
if (v) v->fa=a;
}
a->end+=b->end;
delete b;
return a;
}
node *que[maxm];
struct ac_auto{
node *root;
int size;
ac_auto(){
root=new node;
size=0;
}
void clear(){
int i,l,r;
node *u,*v;
l=r=0;
que[r++]=root;
while (l<r){
u=que[l++];
for (i=0;i<26;i++) if (v=u->ch[i])
que[r++]=v;
delete u;
}
root=new node;
size=0;
}
void add_str(char s[]){
node *u,*v;
int i,id;
u=root;
for (i=0;s[i]!='\0';i++){
id=s[i]-'a';
v=u->ch[id];
if (!v){
v=new node;
u->ch[id]=v;
v->fa=u;
}
u=v;
}
v->end++;
size+=i;
}
void merge_ac(ac_auto &b){
size+=b.size;
root=merge_node(root,b.root);
b.root=new node;
b.size=0;
}
void build_fail(){
int i,l,r;
node *u,*v,*w;
l=0; r=0;
for (i=0;i<26;i++) if (v=root->ch[i]){
v->set_fail(root);
que[r++]=v;
}
while (l<r){
u=que[l++];
for (i=0;i<26;i++) if (v=u->ch[i]){
que[r++]=v;
w=u->fail;
while (w!=root){
if (w->ch[i]){
v->set_fail(w->ch[i]);
break;
}
w=w->fail;
}
if (w==root){
if (w->ch[i])
v->set_fail(w->ch[i]);
else
v->set_fail(root);
}
}
}
}
ll query(char s[]){
node *u;
int i,id;
ll ans;
u=root; ans=0;
for (i=0;s[i]!='\0';i++){
id=s[i]-'a';
while (u!=root){
if (u->ch[id]){
u=u->ch[id];
break;
}
u=u->fail;
}
if (u==root){
if (u->ch[id])
u=u->ch[id];
else u=root;
}
ans+=u->cnt;
}
return ans;
}
};
ac_auto ac[maxn];
char s[maxn];
int main(){
int tt;
int i,j,n,m,op;
int siz;
ll ans;
scanf("%d",&tt);
siz=0;
while (tt--){
scanf("%d%d",&n,&m);
for (i=0;i<siz;i++) ac[i].clear();
for (i=0;i<n;i++){
scanf("%s",s);
ac[0].add_str(s);
}
ac[0].build_fail();
siz=1;
for (i=0;i<m;i++){
scanf("%d%s",&op,s);
if (op==1){
ac[siz++].add_str(s);
while (siz>1&&ac[siz-1].size*2>ac[siz-2].size){
ac[siz-2].merge_ac(ac[siz-1]);
siz--;
}
ac[siz-1].build_fail();
}
else{
ans=0;
for (j=0;j<siz;j++) ans+=ac[j].query(s);
printf("%lld\n",ans);
}
}
}
return 0;
}