题目链接
先给你一个空字符串,每次将该字符串打印一份或者在它的末尾添加或删除一个字符
询问第x次答应的字符串在第y次中出现了多少次
先想到一个显然的暴力,把每次的字符串暴力存下来,然后看一下询问了哪一些模式串,用KMP算法求出next数组后去匹配
但是这太慢了,于是我们考虑用其他字符串数据结构,既然有这么多串,那么先来看看trie或AC自动机(一个东东啦)行不行
我们先来分析一下空间,发现可能很多串都有公共的前缀,每次加一个字符只会增加trie上的一个点,也就是说最后节点数也不会超过1e5,是开得下的.
然后再来看怎么做,一开始觉得是不同的一些模式串去匹配一些不同的文本串,不是很好做
考虑到每次询问的文本串也会被挂在trie上,找一下思路
我们有一个重要的东西:子串等于前缀的后缀!!!!!
于是我们发现一个串T是另一个串S的子串,当且仅当T是S某一个前缀的后缀,并且有多少个前缀满足该条件,T就在S中出现了多少次
我们突然意识到我们求解fail指针的时候就是在干这个事情,求解每一个前缀的最长的后缀等于另一个串的前缀,我们只需要强制只在fail指针指的是某个串的结尾时计算答案就可以了
这样的话我们是对于每一个给的y串,去找它所有前缀能用fail指向串尾的x串
于是我们现在的做法是,记录每一个串在AC自动机上的终止节点,求出fail指针
对于每一次询问,从AC自动机的根上下来,到一个点之后,不停的跳fail指针,把是串尾且包含这个询问的答案更新
但是这样每一次询问都要暴力跳,还是会TLE,怎么办呢?
考虑怎么才能对询问进行批量处理
对于每一次,询问我们是暴力跳fail指针,去找x串的结尾,发现每一个点有且仅有一个fail指针,应该是可以优化这个过程的,如果我们把fail指针理解为父亲指针的话,那么这就成了在一个x串结尾点的子树里面找是询问的点并把他的答案更新
我们构建出一颗fail树,容易发现祖先节点都是该点的后缀
其实并不能对询问进行批量处理,但是我们发现我们简化了求解询问的过程
我们还是单个看询问,那么就只要求子树内标记的点(y的前缀的点)的个数,即为x串的出现次数了,这个东西很好做,求出fail树的dfs序,那么每一个点的子树dfs序连续,用个树状数组维护一下就行了
于是我们的最终做法:在trie上便历一个串的所有前缀,并在fail树上标记,那么一个询问串在该串上的匹配次数就是他的子树(它是子树内点的后缀,一定满足是给的串的子串)内标记过的点的个数了,用BIT求出即可
至于为什么不会重复,因为相同的前缀我们只会标记一个,然后我们是对于一个询问串时,我们只有当它是某个前缀的后缀时才有贡献,这样保证了询问串的末尾位置在原串中不会重复,当然答案就不会算重啦(其实上面已经说过了)
P.S.:打字的过程就是在trie上跳上跳下,不要每次重新从根出发下来,直接模仿打字的过程存储和处理询问就可以了(不然你会T得很惨)
对字符串模式匹配和AC自动机的理解加深了不少….
代码:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=1e5+10;
inline int read(){
register int x=0,t=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
return x*t;
}
namespace AC{
char S[N];int len;
int next[26][N];int cnt=1;
int fail[N];int fa[N];
typedef pair<int,int> Pr;
int end[N];vector V[N];
int n;
struct edge{int to,next;}a[N];
int head[N];int cur;
void init(){
scanf("%s",S+1);len=strlen(S+1);n=0;
register int p=1;
for(register int i=1;i<=len;++i){
if(S[i]>='a'&&S[i]<='z') {
register int k=S[i]-'a';
if(!next[k][p]){next[k][p]=++cnt;fa[cnt]=p;p=cnt;}
else p=next[k][p];
}
else if(S[i]=='B') p=fa[p];
else end[++n]=cnt;
}
}
queue<int> Q;
void add(int x,int y){a[++cur]=(edge){y,head[x]};head[x]=cur;}
void make_fail(){
Q.push(1);
fail[1]=1;
while(!Q.empty()){
register int p=Q.front();Q.pop();
for(register int j=0;j<26;++j){
if(next[j][p]){
if(p==1) fail[next[j][p]]=1;
else{
register int q=fail[p];
while(q!=1&&!next[j][q]) q=fail[q];
if(next[j][q]) fail[next[j][p]]=next[j][q];
else fail[next[j][p]]=1;
}
add(fail[next[j][p]],next[j][p]);
Q.push(next[j][p]);
}
}
}
return;
}
int subend[N];int I=0;int dfn[N];
void dfs(int u){
dfn[u]=++I;
for(register int v,i=head[u];i;i=a[i].next){v=a[i].to;dfs(v);}
subend[u]=I;
}
int tr[N];int ans[N];
#define lowbit(a) ((a)&(-a))
void update(int p,int x){while(p<=I) tr[p]+=x,p+=lowbit(p);return;}
inline int query(int p){
register int res=0;
while(p) res+=tr[p],p-=lowbit(p);
return res;
}
void work(){
make_fail();dfs(1);
register int m;m=read();
for(register int i=1;i<=m;++i){
register int x=read(),y=read();
V[end[y]].push_back(Pr(i,end[x]));
}
n=0;register int p=1;
for(register int i=1;i<=len;++i){
if(S[i]>='a'&&S[i]<='z') p=next[S[i]-'a'][p],update(dfn[p],1);
else if(S[i]=='P'){
++n;
register int ss=V[end[n]].size();
for(register int i=0;iregister Pr P=V[end[n]][i];
ans[P.first]=query(subend[P.second])-query(dfn[P.second]-1);
}
}
else if(S[i]=='B') update(dfn[p],-1),p=fa[p];
}
for(register int i=1;i<=m;++i) printf("%d\n",ans[i]);
}
}
int main()
{
AC::init();AC::work();
}