题面
Description
UPD:本题字符集为全体小写字母
Input
Output
Sample Input
5
1 abc
3 abcabc
0 abc
3 aba
1 abababc
Sample Output
2
2
HINT
题解
这个“强制在线”好假……
法一:
我们如果用暴力做法,就是在 1 1 1和 2 2 2操作时将字符串强制插入 A C AC AC自动机修改对应节点的 v a l val val值,然后在 3 3 3操作时先对自动机做一遍 g e t _ f a i l get\_fail get_fail,再用普通方法 q u e r y query query查询每个字符串在 S S S中出现的次数。
但是我们发现这样会死循环,原因是在第一次 g e t f a i l getfail getfail时,会有如下操作:
t[u].ch[i]=t[t[u].fail].ch[i]
这样有可能会让 c h i l d child child树形成一个环,也就是说这次 g e t _ f a i l get\_fail get_fail完后再插入时跑 c h i l d child child树可能会死循环。
因为插入必须按原来的 c h i l d child child树跑,所以我们就想如何维护如何区分是原 c h i l d child child树还是 g e t _ f a i l get\_fail get_fail后的 c h i l d child child树。我们有注意到如果将 c h i l d child child树分层,每个节点的深度为 d [ i ] d[i] d[i],那么必有 d [ f a i l [ u ] ] < = d [ u ] d[fail[u]]<=d[u] d[fail[u]]<=d[u],因为 f a i l [ u ] fail[u] fail[u]所代表的字符串是 u u u所代表的字符串的一个后缀,所以 d [ c h [ f a i l [ u ] ] ] < = d [ u ] < d [ c h [ u ] ] d[ch[fail[u]]]<=d[u]<d[ch[u]] d[ch[fail[u]]]<=d[u]<d[ch[u]],所以如果新 c h i l d child child树里某个节点的儿子的 d d d比它小,这就不是旧 c h i l d child child树中所含有的儿子,反之,如果新 c h i l d child child树里某个节点的儿子的 d d d比它大,这就是旧 c h i l d child child树中所含有的儿子。
然后就可以处理这个问题了,之后就按上述方法做就行了(但这个算法时间复杂度比较高, 3000 m s 3000ms 3000ms情况下需要卡常)。
做法是同学的,我自己没写代码
法二:
我们发现,这里的“强制在线”仅仅是针对操作类型的,而不是针对字符串的,说明该用到的字符串还是要用到。
那么我们就可以将所有的读入的字符串先插入到 A C AC AC自动机里(不管是询问还是修改,因为你也不知道),再做一遍 g e t _ f a i l get\_fail get_fail,后面直接按常规方法求解就行了。
但我们发现当数据达到最大时,这样的时间复杂度是不行的,所以我们考虑如何维护“修改”和“常规方法求解”。
如何维护呢?我们现将 f a i l fail fail树建出来:
for(int i=1;i<=tot;i++)
add_edge(t[i].fail,i);
那么对于原来的 q u e r y query query代码:
long long query(string s)
{
long long ans=0;
int u=0,len=s.size();
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
u=t[u].ch[v];
for(int now=u;now;now=t[now].fail)ans+=a[now];//不断跳fail的过程
}
return ans;
}
我们发现不断跳 f a i l fail fail加 a n s ans ans的过程就是在 f a i l fail fail树上从 u u u到根加上每个点的 v a l val val值的过程。
然后对于修改,我们发现就是将 S S S的 f a i l fail fail树中的对应点的子树都将 v a l val val值加 1 1 1或减 1 1 1。
以上操作都可以用很多数据结构维护,我选了码量少时间复杂度低的树状数组。
至于如何维护,见注释。
下面是详细代码:
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
struct Trie
{
int ch[26],fail,val;
}t[N];
int m,tot,cnt,idx,opt[N],ed[N],mask,l[N],head[N],to[N<<1],nxt[N<<1],a[N],dfn[N],size[N],c[N];
char s[N];
void adde(int u,int v)//fail树建边
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
int lowbit(int x)//树状数组部分
{
return x&(-x);
}
void add(int x,int y)
{
for(;x<=idx;x+=lowbit(x))c[x]+=y;
}
long long ask(int x)
{
long long ans=0;
for(;x;x-=lowbit(x))ans+=c[x];
return ans;
}
void dfs(int u)//维护树上树状数组
{
dfn[u]=++idx;//先记录遍历fail树的顺序
size[u]=1;//记录子树+自己的节点个数
for(int i=head[u];i;i=nxt[i])
{
dfs(to[i]);
size[u]+=size[to[i]];
}
//当子树遍历完后,我们发现dfn[u]+size[u]-1就是遍历子树时最后一个节点的dfn值,且dfn[u]~dfn[u]+size[u]-1所有的值都是u及其子树某个节点的dfn值,换句话说,u及其子树每个节点的dfn值,都在dfn[u]~dfn[u]+size[u]-1内且一一对应。
}
void insert(int l,int r,int x,int id)//插入字符串
{
int u=0;
for(register int i=l;i<=r;i++)
{
int v=s[i]-'a';
if(!t[u].ch[v])
t[u].ch[v]=++tot;
u=t[u].ch[v];
}
t[u].val+=x;
ed[id]=u;
}
void getfail()//get_fail+fail树建边
{
queue<int>q;
for(register int i=0;i<26;i++)if(t[0].ch[i])q.push(t[0].ch[i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(register int i=0;i<26;i++)
{
int p=t[t[u].fail].ch[i];
if(t[u].ch[i])
{
t[t[u].ch[i]].fail=p;
q.push(t[u].ch[i]);
}
else t[u].ch[i]=p;
}
}
for(int i=1;i<=tot;i++)
adde(t[i].fail,i);
}
long long query(int l,int r)//询问
{
long long ans=0;
int u=0;
for(register int i=l;i<=r;i++)
{
int v=s[i]-'a';
u=t[u].ch[v];
ans+=ask(dfn[u]);
}
return ans;
}
int main()
{
scanf("%d",&m);
char str[N];
for(register int i=1;i<=m;i++)
{
scanf("%d",&opt[i]);
scanf("%s",str+1);
l[i]=l[i-1]+strlen(str+1);
for(int j=l[i-1]+1;j<=l[i];j++)
s[j]=str[j-l[i-1]];
insert(l[i-1]+1,l[i],0,i);
}
getfail();
dfs(0);
for(register int i=1;i<=m;i++)
{
opt[i]^=mask;
if(opt[i]==1)
{
add(dfn[ed[i]],1);
add(dfn[ed[i]]+size[ed[i]],-1);//区间树状数组修改
}
if(opt[i]==2)
{
add(dfn[ed[i]],-1);
add(dfn[ed[i]]+size[ed[i]],1);
}
if(opt[i]==3)
{
long long ans=query(l[i-1]+1,l[i]);
printf("%lld\n",ans);
mask^=abs(ans);
}
}
return 0;
}
总结
不要被题目的假象所迷惑,要看清题目的本质。
g e t _ f a i l get\_fail get_fail后 c h i l d child child树不能直接跑。
建 f a i l fail fail树是解决 A C AC AC自动机上的询问的一种好方法。