题目链接
http://codeforces.com/problemset/problem/710/F
题意
维护一个字符串集合,支持三种操作:
1.加字符串
2.删字符串
3.查询集合中的所有字符串在给出的模板串中出现的次数
操作数&字符串总长(≤3×10^5)
思路
看到多串匹配考虑用AC自动机。
先考虑删除,这个很好办,我们可以维护两个AC自动机,一个记录插入,一个记录删除,将串在两个上面分别跑再做差就好了。这样子删除也变成了插入。
题目难点主要是插入,对于每个新来的串都重构,(TLE)是肯定的。
于是乎就有了二项堆的思想:维护二项堆核心思想就是二进制分组(第(i)个堆拥有(2^i)个元素,第(i)个堆可以由两个第(i−1)个堆合并而成)
类似的,这里可以将堆换为AC自动机:(i)号AC自动机维护(2^i)个串,若有两个(i)号AC自动机则合并成一个(i+1)号AC自动机,合并是拆除原AC自动机,然后暴力合并。这样复杂度就变成(log)的了
#include<bits/stdc++.h>
using namespace std;
const int maxx = 3e5+10;
struct ac
{
int trie[maxx][26],tmp[maxx][26];
int fail[maxx],vis[maxx],rt[maxx],siz[maxx],sum[maxx];
int tot,top;
void getfail(int x)
{
queue<int>q;
for(int i=0;i<26;i++)
{
tmp[x][i]=trie[x][i];
if(tmp[x][i])fail[tmp[x][i]]=x,q.push(tmp[x][i]);
else tmp[x][i]=x;
}
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<26;i++)
{
tmp[u][i]=trie[u][i];
if(tmp[u][i])fail[tmp[u][i]]=tmp[fail[u]][i],q.push(tmp[u][i]);
else tmp[u][i]=tmp[fail[u]][i];
}
sum[u]=vis[u]+sum[fail[u]];
}
}
int Merge(int x,int y)
{
if(!x||!y)return x+y;
vis[x]+=vis[y];
for(int i=0;i<26;i++)trie[x][i]=Merge(trie[x][i],trie[y][i]);
return x;
}
void Insert(string s)
{
rt[++top]=++tot;
siz[top]=1;
int now=rt[top];
for(int i=0;i<s.size();i++)
{
int id=s[i]-'a';
if(!trie[now][id])trie[now][id]=++tot;
now=trie[now][id];
}
vis[now]=1;
while(top&&siz[top]==siz[top-1]) //二进制分组合并
{
top--;
rt[top]=Merge(rt[top],rt[top+1]);
siz[top]+=siz[top+1];
}
getfail(rt[top]);
}
int query(string s)
{
int ans=0;
for(int i=1;i<=top;i++) //将当前所有分组的结果累加就行了
{
int now=rt[i];
for(int j=0;j<s.size();j++)
{
int id=s[j]-'a';
now=tmp[now][id];
ans+=sum[now];
}
}
return ans;
}
}ac1,ac2;
int main()
{
int n;
cin>>n;
int op;
string s;
while(n--)
{
cin>>op>>s;
if(op==1)ac1.Insert(s);
else if(op==2)ac2.Insert(s);
else printf("%d
",ac1.query(s)-ac2.query(s));
}
return 0;
}