大致题意: 给你一个由小写字母和(B,P)组成的操作串,其中(B)表示回删,(P)表示打印。(m)组询问,问你第(x)个被打印出来的串在第(y)个被打印出来的串中出现了几次。
(AC)自动机
这是一道(AC)自动机的题目,个人感觉不是特别难的样子,我居然自己做出来了......
显然,我们可以对题目中给出的操作串建出一个包含所有串的(AC)自动机((B)操作就相当于是回到父节点)。
然后考虑如何处理询问。
我们需要知道,在(AC)自动机上有这样两个性质:
- (A)串是(B)串的前缀,说明(A)串在(AC)自动机上对应的节点在根节点到(B)串的路径上。
- (A)串是(B)串的后缀,说明(B)串在(AC)自动机上对应的节点跳了若干次(fail)指针后能够到达(A)串。
现在要求(B)串中(A)串的出现次数,就相当于求(B)串中有多少个前缀满足(A)串是它的后缀。
考虑跳了若干次(fail)指针后能够到达(A)串,说明在(AC)自动机的(fail)树上(A)串所对应的节点是其父节点。
所以,如果我们给根节点到(B)串所对应节点的路径上的所有节点打上(1)的标记,那么就相当于询问(A)串所对应节点在(fail)树上的子树和。
而如何做到快速打标记呢?
考虑这道题给出的字符串之间是有联系的。
即,我们可以离线做。只要再次模拟操作串,每到达一个新的节点就将这个点权值加(1),回退一次就将这个点权值减(1)。每到达一个打印串,就处理以它作为(y)的询问。
至于如何求子树和,只要预处理出(fail)树的(dfs)序,然后树状数组维护即可。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,len,Nt,Qt,ans[N+5];char s[N+5];
struct Qry
{
int x,y,id;I Qry(CI a=0,CI b=0,CI p=0):x(a),y(b),id(p){}
I bool operator < (Con Qry& o) Con {return y<o.y;}//离线,按y排序
}Q[N+5];
namespace Tree//fail树
{
class TreeArray//树状数组,用于求子树和
{
private:
int a[N+5];
I int Qry(RI x) {RI t=0;W(x) t+=a[x],x-=x&-x;return t;}
public:
I void Add(RI x,CI y) {W(x<=Nt) a[x]+=y,x+=x&-x;}
I int Qry(CI x,CI y) {return Qry(y)-Qry(x-1);}
}A;
int ee,lnk[N+5],d,dI[N+5],dO[N+5];struct edge {int to,nxt;}e[N<<1];
I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}//建边
I void Init(CI x,CI lst=0)//预处理dfs序
{
dI[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt)
e[i].to^lst&&(Init(e[i].to,x),0);dO[x]=d;
}
I void Add(CI x,CI v) {A.Add(dI[x],v);}//单点修改
I int Qry(CI x) {return A.Qry(dI[x],dO[x]);}//询问子树和
}
namespace AcAutomation//AC自动机
{
int pos[N+5],q[N+5];struct node {int Nxt,F,S[30];}O[N+5];
I void Init()//初始化,模拟操作串
{
O[1].F=Nt=1;for(RI i=1,x=1,y;i<=len;++i)
{
if(s[i]=='B') {x=O[x].F;continue;}if(s[i]=='P') {pos[++n]=x;continue;}
!O[x].S[y=s[i]&31]&&(O[O[x].S[y]=++Nt].F=x),x=O[x].S[y];
}
}
I void Build()//求出fail指针,并建树
{
RI i,k,H=1,T=0;for(i=1;i<=26;++i)
(O[1].S[i]?O[q[++T]=O[1].S[i]].Nxt:O[1].S[i])=1;
W(H<=T) for(k=q[H++],i=1;i<=26;++i)
(O[k].S[i]?O[q[++T]=O[k].S[i]].Nxt:O[k].S[i])=O[O[k].Nxt].S[i];
for(i=2;i<=Nt;++i) Tree::add(O[i].Nxt,i);Tree::Init(1);//建树
}
I void Solve()//离线处理询问
{
for(RI i=1,x=1,m=0,k=1;i<=len;++i)
{
if(s[i]=='B') {Tree::Add(x,-1),x=O[x].F;continue;}//回退将权值减1
if(s[i]^'P') {x=O[x].S[s[i]&31],Tree::Add(x,1);continue;}//到达新的节点将权值加1
++m;W(Q[k].y==m) ans[Q[k].id]=Tree::Qry(pos[Q[k].x]),++k;//处理以当前打印串为y 的询问
}
}
}
int main()
{
using namespace AcAutomation;
RI i;scanf("%s",s+1),len=strlen(s+1),Init(),Build();
for(scanf("%d",&Qt),i=1;i<=Qt;++i) scanf("%d%d",&Q[i].x,&Q[i].y),Q[i].id=i;
for(sort(Q+1,Q+Qt+1),Solve(),i=1;i<=Qt;++i) printf("%d
",ans[i]);return 0;
}