测试地址:GRE Words Revenge
题目大意:维护以下操作:1.在词典中加入一个单词。2.询问一个字符串中有多少个子串是词典中的单词。强制在线。
做法:本题需要用到AC自动机+分块。
如果离线,我们可以直接把所有涉及的单词先加入到trie中,然后建AC自动机,在运行的时候打标记和匹配即可。
但这里强制在线,我们知道AC自动机本身是不能支持在线插入操作的,所以每次插入后我们需要暴力重构AC自动机,重构一次的时间复杂度是的,最坏时间复杂度就是,难以接受。
注意到,插入的时间复杂度是均摊的,重构的复杂度是均摊的,看到这样悬殊的复杂度,我们就想到用分块思想来降低瓶颈,也就是重构操作的复杂度。
具体来说,我们维护一大一小两个AC自动机,给小的AC自动机设定一个容量为。每次插入一个单词,先往小的自动机中插入,每次插入后暴力重构小自动机。如果一次插入后,小自动机中的点数超过容量,就暴力把小自动机中的信息加入到大自动机中,然后重构大自动机。
现在我们来分析时间复杂度:对于插入操作,每个点最多被加入两次(小自动机,大自动机),因此时间复杂度是。对于重构操作,有次对小自动机的重构,时间复杂度为,还有次对大自动机的重构,时间复杂度也为。
这样我们就能在线维护出AC自动机了。处理询问的方法和传统的AC自动机类似,在两个自动机上都匹配一遍即可(当然,需要在构建AC自动机的时候求出一个表示每个点贡献值的数组),但是需要注意一点:同一个单词不应该在两边同时拥有贡献,不然会算重。这样我们就完成了这一题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,rt0,rt1,buf,len,tot;
int ch[200010][2]={0},fail[200010];
int q[200010],h,t;
ll cnt[200010],ans;
char s[10000010];
bool word[200010]={0};
void insertbuf(int &v1,int v2,int step)
{
if (!v1)
{
v1=++tot;
buf++;
ch[v1][0]=ch[v1][1]=word[v1]=0;
}
if (step>=len)
{
if (!word[v2]) word[v1]=1;
else word[v1]=0;
return;
}
bool f=s[step]-'0';
insertbuf(ch[v1][f],ch[v2][f],step+1);
}
void insertmajor(int &v1,int v2)
{
if (!v1)
{
v1=++tot;
ch[v1][0]=ch[v1][1]=word[v1]=0;
}
word[v1]|=word[v2];
if (ch[v2][0]) insertmajor(ch[v1][0],ch[v2][0]);
if (ch[v2][1]) insertmajor(ch[v1][1],ch[v2][1]);
}
void rebuild(int rt)
{
h=1,t=0;
cnt[rt]=0;
for(int i=0;i<=1;i++)
if (ch[rt][i])
{
fail[ch[rt][i]]=rt;
cnt[ch[rt][i]]=cnt[rt]+word[ch[rt][i]];
q[++t]=ch[rt][i];
}
while(h<=t)
{
int v=q[h++];
for(int i=0;i<=1;i++)
if (ch[v][i])
{
int p=fail[v];
while(p!=rt&&!ch[p][i]) p=fail[p];
if (ch[p][i]) fail[ch[v][i]]=ch[p][i];
else fail[ch[v][i]]=rt;
cnt[ch[v][i]]=cnt[fail[ch[v][i]]]+word[ch[v][i]];
q[++t]=ch[v][i];
}
}
}
void match(int rt)
{
int now=rt,i=1;
while(i<len)
{
bool f=s[i++]-'0';
while(now!=rt&&!ch[now][f]) now=fail[now];
if (ch[now][f]) now=ch[now][f];
else now=rt;
ans+=cnt[now];
}
}
void decrypt()
{
int k=ans%(len-1);
for(int i=1;i<=k;i++)
s[len-1+i]=s[i];
for(int i=1;i<=len;i++)
s[i]=s[i+k];
}
int main()
{
scanf("%d",&T);
int Case=0;
while(T--)
{
Case++;
printf("Case #%d:
",Case);
rt0=1,rt1=2;
buf=0;ans=0;
ch[rt0][0]=ch[rt0][1]=ch[rt1][0]=ch[rt1][1]=0;
tot=2;
scanf("%d",&n);
int blocksiz=400;
for(int i=1;i<=n;i++)
{
scanf("%s",s);
if (s[0]=='+')
{
len=strlen(s);
decrypt();
insertbuf(rt1,rt0,1);
if (buf>blocksiz)
{
insertmajor(rt0,rt1);
rebuild(rt0);
ch[rt1][0]=ch[rt1][1]=0;
buf=0;
}
else rebuild(rt1);
}
else
{
len=strlen(s);
decrypt();
ans=0;
match(rt0),match(rt1);
printf("%lld
",ans);
}
}
}
return 0;
}