【BZOJ】2434: [Noi2011]阿狸的打字机-AC自动机&fail树&BIT

传送门:bzoj2434


题解

最近终于填了ac自动机的坑qwq。
先来大暴力,我们扫一遍当前询问的y串,一个一个判断。
可以发现,对于当前节点,若不停的跳fail链直到根节点的过程中,我们可以跳到x串的末节点,那么就可以将答案+1。
同时发现一些显然的事实,每个节点只会有一个fail点,而每个点可能没有或有多个点的fail指向它。所有的点跳fail链最终会回到root,且每个点跳fail的时候并不会进入环。
那么统计x串在y串中的出现次数就变成了统计x串的反向fail链中的节点有多少个在y串中。
这样会发现很像树的性质。我们就完全可以利用这样的性质按fail链反向建一颗一root为根的fail树。
通过ac自动机fail指针构建方法可以得到,在x串的末尾节点的fail树的子树内,在某一特定串上的点必然是dfs序上一段连续的区间(也是树形态上的一条链)。
那么就可以离线询问,O(n)扫一遍,用BIT在对应区间加一下,弹出的时候减一下即可。


代码

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<cctype>
#define pb(x) push_back((x))
using namespace std;
const int N=150050;
int n,m;
char s[N];
int head[N],to[N],nxt[N],tot,in[N],ot[N],dfn,fa[N];
int ch[N][26],bit[N],cnt,pos[N],f[N],ans[N],num,ret;
queue<int>Q;
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
struct ms{int id,v;}tp;
vector<ms>q[N];

inline int rd()
{
	char ch=getchar();int x=0,f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
	return x*f;
}

inline void dfs(int x)
{
	in[x]=++dfn;
	for(int i=head[x];i;i=nxt[i]) dfs(to[i]);
	ot[x]=dfn;
}

inline void ad(int x,int as)
{for(;x<=dfn;x+=(x&(-x))) bit[x]+=as;}
inline int get(int x)
{for(ret=0;x;x-=(x&(-x)))ret+=bit[x];return ret;}
int main(){	
    int i,j,k,kmp,u=0,alp,t;
	scanf("%s",s);n=strlen(s);
	for(i=0;i<n;++i){
		if(s[i]=='P') pos[++num]=u;
		else if(s[i]=='B') u=fa[u];
		else{alp=s[i]-'a';if(!ch[u][alp]) ch[u][alp]=++cnt;fa[ch[u][alp]]=u;u=ch[u][alp];}
 	}
 	for(i=0;i<26;++i) if(ch[0][i]) Q.push(ch[0][i]);
 	while(!Q.empty()){
 		u=Q.front();Q.pop();kmp=f[u];
 		for(i=0;i<26;++i)
 		 if(ch[u][i]){f[ch[u][i]]=ch[kmp][i];Q.push(ch[u][i]);}
 		 else{ch[u][i]=ch[kmp][i];}
 	}
 	for(i=1;i<=cnt;++i) lk(f[i],i); 
 	m=rd();
 	for(u=1;u<=m;++u){i=rd();j=rd();tp.v=i;tp.id=u;q[j].pb(tp);}
 	dfs(0);
 	num=0;ad(in[0],1);u=0;
 	for(i=0;i<n;++i){
 		if(s[i]=='P'){
		   num++;
		   for(j=q[num].size()-1;j>-1;--j){
		   	 k=pos[q[num][j].v];t=q[num][j].id;
		   	 ans[t]=get(ot[k])-get(in[k]-1);
		   }
		 }else if(s[i]=='B') {ad(in[u],-1);u=fa[u];}
		 else{
		 	alp=s[i]-'a';u=ch[u][alp];
		 	ad(in[u],1);
		 }
 	}
 	for(i=1;i<=m;++i) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值