NOI2011 阿狸的打字机 AC自动机+fail树

原题:https://www.luogu.org/problemnew/show/P2414

题解:给出一个字符串,从中可以拆出n个模式串,m组询问:第x个字符串在第y个字符串中出现了几次。暴力的做法,对于y的前缀,查询失败指针中x出现了几次。很容易想到离线的做法将y排序,查找y前缀的失败指针,用桶维护一下,可以拿到70分。

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
const int Z=26;
struct node{int a,b,p;}b[N];
char s[N],stack[N];
int top,cnt,tot,m,ans[N],ch[N][Z],fa[N],nxt[N],end[N],f[N],sum[N];
bool mark[N];
bool cmp(node x,node y){
	return x.b==y.b?x.a<y.a:x.b<y.b;
}
inline void ins(char *s,int ind,int len){
	int p=1;
	for(int i=1;i<=len;i++){
		int c=s[i]-'a';
		if(!ch[p][c]) ch[p][c]=++tot;
		fa[ch[p][c]]=p;
		p=ch[p][c];		
	}
	mark[p]=1;end[ind]=p;f[p]=ind;
}
void  bfs(){
	queue<int> q;
	for(int i=0;i<Z;i++) ch[0][i]=1;
	q.push(1);nxt[1]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<Z;i++){
			if(!ch[u][i]) ch[u][i]=ch[nxt[u]][i];
			else{
				q.push(ch[u][i]);
				int v=nxt[u];
				while(v>0 && !ch[v][i]) v=nxt[v];
				nxt[ch[u][i]]=ch[v][i];	
			}
		}	
	}
}
inline int query(int x,int y){
	int ans=0;
	for(int k=end[y];k>1;k=fa[k]){
		for(int p=k;p>1;p=nxt[p])
		 	if(p==end[x]){ans++;break;}
	}
	return ans;
}
int main(){
//	freopen("type2.in","r",stdin);
	scanf("%s",s+1);
	top=0;cnt=0;tot=1;
	for(int i=1;i<=strlen(s+1);i++){
		if(s[i]>='a' && s[i]<='z') stack[++top]=s[i];
		if(s[i]=='B') top--;
		if(s[i]=='P'){
			cnt++;ins(stack,cnt,top);
		}
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&b[i].a,&b[i].b),b[i].p=i;	
	bfs();
	sort(b+1,b+m+1,cmp);
	int last=-1;
	for(int i=1;i<=m;i++){
		int x=b[i].a;int y=b[i].b;
		if(last!=y) {
			memset(sum,0,sizeof sum);
			for(int k=end[y];k>1;k=fa[k])
				for(int p=k;p>1;p=nxt[p]) if(f[p]) sum[f[p]]++;	
			last=y;
		}
		ans[b[i].p]=sum[x];
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

对于每个y都要查所有的失败指针显然是不能接受的,将失败指针反向,对于x的子树中有多少是y的前缀就是答案,可以做深搜序,差分,用树状数组维护区间的和来解决。同时注意建字典树时可以根据第一行的字符串来进行点修改。

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+1000;
const int Z=26;
struct node{int a,b,p;}b[N];
struct E{
	int x,y,next;
}mm[N];
char s[N];
int ans[N],ch[N][Z],fa[N],nxt[N],end[N],f[N],h[N],l[N],r[N],bit[N];
int nn,top,cnt,tot,m,num;
bool mark[N],vis[N];
bool cmp(node x,node y){return x.b==y.b?x.a<y.a:x.b<y.b;}
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int c){for(x;x<=num;x+=lowbit(x)) bit[x]+=c;}
inline int get(int x){
	int ans=0;
	for(x;x>=1;x-=lowbit(x)) ans+=bit[x];
	return ans;
}
void  bfs(){
	queue<int> q;
	for(int i=0;i<Z;i++) ch[0][i]=1;
	q.push(1);nxt[1]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<Z;i++){
			if(!ch[u][i]) ch[u][i]=ch[nxt[u]][i];
			else{
				q.push(ch[u][i]);
				int v=nxt[u];
				while(v>0 && !ch[v][i]) v=nxt[v];
				nxt[ch[u][i]]=ch[v][i];	
			}
		}	
	}
}
inline void insE(int x,int y){mm[++nn].x=x;mm[nn].y=y;mm[nn].next=h[x];h[x]=nn;}
void dfs(int u){
	l[u]=++num; vis[u]=1;
	for(int k=h[u];k;k=mm[k].next){
		int v=mm[k].y;
		if(!vis[v]) dfs(v); 
	}
	r[u]=num;
	return ;
}
int main(){
//	freopen("type2.in","r",stdin);
//	freopen("test.out","w",stdout);
	top=0;cnt=0;tot=1;memset(h,0,sizeof h);nn=0;num=0;
	scanf("%s",s+1);
	for(int i=1,p=1;i<=strlen(s+1);i++){
		if(s[i]>='a' && s[i]<='z'){
			int c=s[i]-'a';
			if(!ch[p][c]) ch[p][c]=++tot;
			fa[ch[p][c]]=p;
			p=ch[p][c];
		}
		if(s[i]=='B') p=fa[p];
		if(s[i]=='P'){
			cnt++;end[cnt]=p;f[p]=cnt;
		}
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&b[i].a,&b[i].b),b[i].p=i;	
	bfs();
	for(int i=2;i<=tot;i++) insE(nxt[i],i);//,printf("%d %d\n",i,nxt[i]);
	dfs(1);
	sort(b+1,b+m+1,cmp);
	for(int i=1,j=1,now=0,p=1;i<=strlen(s+1);i++){
		if(s[i]=='P'){
			now++;
			for(;j<=m&&b[j].b<=now;j++) {
				int x=b[j].a;
				ans[b[j].p]=(get(r[end[x]])-get(l[end[x]]-1));
			}
		}	
		if(s[i]=='B'){add(l[p],-1);p=fa[p];} 
		if(s[i]>='a' && s[i]<='z')p=ch[p][s[i]-'a'],add(l[p],1);
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值