题意:
给你一个读入串,其中包含多个字符串。字符集是小写字母,如果读到B,表示当前串删除上一个字母,如果读到P,表示当前串形成了一个新串,但是原来的的字符串并不消失,会继续成为下一个串的前缀。我们给每个串按产生的顺序标上序号,每次会给出两个询问串的标号,问第x个串在第y个串中出现了多少次。总串长<=1e5,询问次数<=1e5。
题解:
是一个我觉得写起来有点麻烦的题。
首先问题是个出现次数题,涉及到字符串匹配,最常用的就是KMP和AC自动机,于是优先从这两个算法中考虑。由于是多个串和1e5级别的询问,所以没法用KMP这种单串匹配的方法去每次两两计算答案,于是考虑能不能用AC自动机来做。
我们考虑建出AC自动机。我们发现所有串的总串长可能很长,没法一个一个加,但是我们发现,加上一个字符、删去一个字符,都可以看作在一棵trie树上走,于是不用每次把串拿出来重新插入,只要在之前的基础上继续在trie树上走就好了。这样就可以建出trie树了。这个题题目没有说,但是据说数据保证了每个串都是本质不同的,于是我就懒得写可能有本质相同的情况了。
我们考虑其实如果A串在B串中出现过,那么我们建出AC自动机的fail树之后,应该trie树上表示B串的某个字符的点一定会经过若干次fail指针的跳跃,跳到A串在trie树上的结束节点。而如果A在B中多次出现,那么一定会有多个B串中的点能经过若干次沿着fail指针的跳跃跳到A的结束节点。我们知道,fail指针在形态上会形成一个树形结构,于是我们可以发现,其实A在B中出现的次数,就是trie树上从根到B串的结束节点的路径上,有多少个点在A的结束节点在fail树上的子树内。这些可能还是可以想到的,但是怎么数一个串在fail树上某一个点的子树内有多少个点呢?
我们知道,子树有一个性质是dfs序是连续的一段,我们把fail树建出来,然后求出trie树上每一个点在fail树上的dfs序,我们考虑维护这个dfs序。我们的做法是,把询问离线下来,然后在trie树上一边dfs一边做。dfs到了某一个节点,如果这个节点是一个结束节点,那么就回答所有某个串出现在当前这个节点表示的串中的次数。我们用一个树状数组维护dfs序,到了一个点就把他的dfs序加入树状数组,dfs完了整个子树就把这个点从树状数组中减去,就能在到了trie树上的一个串的结束节点时,维护的信息正好是这个串的信息。那么回答时我们只需要查询问的串在fail树上所在的子树的那一段dfs序的区间和就可以了。
这样这个题就做完了,总复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。说清楚都挺麻烦的,写起来也不是很好写的。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,ans[200010],u[200010],v[200010],cnt,cur,num,cnt1;
int fa[200010],ji[200010],hed[400010],hed1[400010],xu[400010];
int vis[400010],val[400010],ed[400010];
char s[200010];
struct node
{
int fail,vis[26],ed,c;
}tr[400010];
struct edge
{
int to,next,id;
}a[400010],b[400010];
queue<int> q;
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void add(int from,int to,int id)
{
a[++cnt].to=to;
a[cnt].id=id;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void build()
{
for(int i=0;i<=25;++i)
{
if(tr[1].vis[i])
{
q.push(tr[1].vis[i]);
tr[tr[1].vis[i]].fail=1;
}
}
tr[1].fail=1;
while(!q.empty())
{
int x=q.front();
q.pop();
if(tr[x].fail!=1)
{
int gg=tr[fa[x]].fail;
while(1)
{
if(tr[gg].vis[tr[x].c]!=0)
{
gg=tr[gg].vis[tr[x].c];
break;
}
if(gg==1)
break;
gg=tr[gg].fail;
}
tr[x].fail=gg;
}
for(int i=0;i<=25;++i)
{
if(tr[x].vis[i])
q.push(tr[x].vis[i]);
}
}
}
inline void add1(int from,int to)
{
b[++cnt1].to=to;
b[cnt1].next=hed1[from];
hed1[from]=cnt1;
}
inline void dfs(int x)
{
vis[x]=1;
xu[x]=++num;
for(int i=hed1[x];i;i=b[i].next)
{
int y=b[i].to;
if(vis[y])
continue;
dfs(y);
}
ed[x]=num;
}
inline void update(int x,int opt)
{
if(opt==1)
{
for(int i=x;i<=cnt;i+=(i&(-i)))
val[i]++;
}
else
{
for(int i=x;i<=cnt;i+=(i&(-i)))
val[i]--;
}
}
inline int query(int x)
{
int res=0;
for(int i=x;i>=1;i-=(i&(-i)))
res+=val[i];
return res;
}
inline void dfs2(int x)
{
update(xu[x],1);
if(tr[x].ed)//ÀÁµÃд¿ÉÄÜÓб¾ÖÊÏàͬµÄ´®µÄÇé¿öÁË
{
for(int i=hed[tr[x].ed];i;i=a[i].next)
{
int y=a[i].to,z;
z=ji[y];
ans[a[i].id]=query(ed[z])-query(xu[z]-1);
}
}
for(int i=0;i<=25;++i)
{
if(tr[x].vis[i])
dfs2(tr[x].vis[i]);
}
update(xu[x],-1);
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
m=read();
for(int i=1;i<=m;++i)
{
u[i]=read();
v[i]=read();
add(v[i],u[i],i);
}
cur=1;
cnt=1;
for(int i=1;i<=n;++i)
{
if(s[i]=='P')
{
ji[++num]=cur;
tr[cur].ed=num;
}
else if(s[i]=='B')
cur=fa[cur];
else
{
int x=s[i]-'a';
if(!tr[cur].vis[x])
{
tr[cur].vis[x]=++cnt;
tr[cnt].c=x;
fa[tr[cur].vis[x]]=cur;
}
cur=tr[cur].vis[x];
}
}
build();
for(int i=2;i<=cnt;++i)
{
add1(i,tr[i].fail);
add1(tr[i].fail,i);
}
num=0;
dfs(1);
dfs2(1);
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return 0;
}