一、题目
二、解法
本题要求强制在线,但是蒟蒻作者只会离线,也讲一讲吧,虽然和本题关系不大,不想听的巨佬可以跳过这一部分。
可以先把所有 1 1 1操作的字符串插入 AC \text{AC} AC自动机,然后回到第一步,插入和删除都可以看作对 f a i l fail fail字符的区间加减,询问直接在当前时刻的自动机上跳就行了。
那离线怎么做呢?首先删除可以理解成插入
自动机和删除
自动机的差分,那删除也就变成了插入。我们把字符串二进制分组,也就是维护
log
n
\log n
logn个
AC
\text{AC}
AC自动机,每次插入把最低位的
0
0
0后面的
1
1
1并到上面来,然后重建这个自动机的
f
a
i
l
fail
fail指针,询问时在每一个自动机上跑,把答案累加就行了。
这种算法的复杂度是多少呢?由于每个字符串只会被重构 log n \log n logn次,所以总时间复杂度是 O ( n log n ) O(n\log n) O(nlogn),口胡可能难以理解,请参考我的代码。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M = 300005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,op;char s[M];
struct automaton
{
int cnt,rt[20],c[M][26],to[M][26],val[M],fail[M];
automaton() {cnt=20;}
void merge(int x,int y)
{
val[y]|=val[x];
for(int i=0;i<26;i++)
{
if(!c[x][i]) continue;
if(!c[y][i]) c[y][i]=c[x][i];
else merge(c[x][i],c[y][i]);
}
}
void build(int rt)
{
queue<int> q;
fail[rt]=0;
for(int i=0;i<26;i++)
if(c[rt][i]) fail[to[rt][i]=c[rt][i]]=rt,q.push(c[rt][i]);
else to[rt][i]=rt;
while(!q.empty())
{
int t=q.front();
q.pop();
val[t]+=val[fail[t]];
for(int i=0; i<26; i++)
if(c[t][i]) fail[to[t][i]=c[t][i]]=to[fail[t]][i],q.push(c[t][i]);
else to[t][i]=to[fail[t]][i];
}
}
void ins(char *s)
{
int len=strlen(s),p=20;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!c[p][v]) c[p][v]=++cnt;
p=c[p][v];
}val[p]=1;
for(int i=0;i<19;i++)
{
if(!rt[i])
{
rt[i]=i+1;
memcpy(c[rt[i]],c[20],sizeof c[20]);
memset(c[20],0,sizeof c[20]);
build(rt[i]);
break;
}
else
{
merge(rt[i],20);
rt[i]=0;
}
}
}
int query(char *s)
{
int len=strlen(s),ans=0;
for(int i=0;i<19;i++)
{
if(rt[i]==0) continue;
int p=rt[i];
for(int j=0;j<len;j++)
{
p=to[p][s[j]-'a'];
ans+=val[p];
}
}
return ans;
}
}A,D;
int main()
{
n=read();
while(n--)
{
op=read();scanf("%s",s);
if(op==1) A.ins(s);
if(op==2) D.ins(s);
if(op==3) printf("%d\n",A.query(s)-D.query(s));
}
}