阿狸的打字机(bzoj2434,codevs1946,洛谷2414)

1.分析

朴素的算法是把所有字符串处理出来后对于每个询问进行一次kmp,然并卵,肯定超时。

因为有很多字符串,我们可以考虑建立一个AC自动机。这样我们又有了一种思路,假如顺着某个节点的fail指针走,最后可以走到某个字符串的末尾,那么表示这个节点所在的字符串里包括一个那个能走到末尾的字符串。不信看图:

可是这样子搞还是太慢了。

我们可以根据反向的fail指针建立一棵fail树,这样的话一个节点子树里的所有节点就是走fail可以到达它的,那么我们就想到了dfs序,用树状数组维护。

不过怎么计算呢?我们可以预先存下所有的询问。

我们开始遍历这棵trie树(遍历就按照打字机顺序即可,我没有是因为懒得改了),每到一个节点,就在树状数组里这个节点dfs序入序的位置+1,每离开一个节点,就-1。

假如这个节点是一个字符串的末尾,那么就处理询问,此时树状数组里存的肯定是关于这整个字符串的信息。处理询问是比如是aaa我们处理的询问是其他字符串在aaa里出现了几次这样的询问,答案就是哪个字符串dfs出序和入序之间的那个区间里的值的和。

2.代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iomanip>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
char s[100005];
int fa[100005],a[100005][27],ne[100005],bh[100005];
int que[100005],ks[100005],ans[100005],sum[100005],mo[100005];//ks:询问的开始处
struct fil{int h,in,lif;}tree[100005];
struct tx{int xia;int go;}edg[100005],qus[100005];
int m,sz=1,tot=0,ti,cnt=0;//tot:边的数量
int read(){
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){q=q*10+ch-'0',ch=getchar();}
	return q;
}
void tri(){
	int i,j,t,from=1,las=1,l=strlen(s);
	for(i=0;i<l;i++){
		if(s[i]=='P'){cnt++;mo[cnt]=from;bh[from]=cnt;}
		else if(s[i]=='B'){from=fa[from];}
		else {
			t=s[i]-'a'+1;
			if(a[from][t])from=a[from][t];
			else {sz++;fa[sz]=from;a[from][t]=sz;from=sz;}
			}
	}
}
void add(int x,int y){tot++;edg[tot].go=y;edg[tot].xia=tree[x].h;tree[x].h=tot;}
void ac(){//构建fail树?
	int i,j,head=1,tail=1,from;
	que[1]=1;
	while(head<=tail){
		from=que[head];
		for(i=1;i<=26;i++){
			if(!a[from][i])continue;
			j=ne[from];
			while(!a[j][i])j=ne[j];
			ne[a[from][i]]=a[j][i];add(a[j][i],a[from][i]);
			tail++;que[tail]=a[from][i];
		}
		head++;
	}
}
void dfs(int x){//求dfs序
	int i;
	ti++;tree[x].in=ti;
	for(i=tree[x].h;i;i=edg[i].xia)dfs(edg[i].go);
	tree[x].lif=ti;
}
int lowbit(int x){return x&(-x);}
int jia(int x){int i;while(x<=sz){sum[x]++;x+=lowbit(x);}}
void jian(int x){int i;while(x<=sz){sum[x]--;x+=lowbit(x);}}
int getans(int x){
	int da=0;
	while(x>0){da+=sum[x];x-=lowbit(x);}
	return da;
}
void goin(int x){
	int i,kl;
	if(x!=1)jia(tree[x].in);
	for(i=1;i<=26;i++)
		if(a[x][i])goin(a[x][i]);
	if(bh[x]){
		for(i=ks[bh[x]];i!=0;i=qus[i].xia){kl=mo[qus[i].go];
		ans[i]=getans(tree[kl].lif)-getans(tree[kl].in-1);}
	}
	if(x!=1)jian(tree[x].in);
}
int main()
{
   	int i,j,l,x,y;
   	scanf("%s",s);
   	for(i=1;i<=26;i++)a[0][i]=1;
   	tri();ac();dfs(1);
   	m=read();
   	for(i=1;i<=m;i++){
   		x=read();y=read();
   		qus[i].go=x;qus[i].xia=ks[y];ks[y]=i;
   	}
   	goin(1);
   	for(i=1;i<=m;i++)printf("%d\n",ans[i]);
   	return 0;
} 


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值