洛谷 P5357 【模板】AC自动机(二次加强版)

传送门
想用后缀数组和后缀自动机来搞这道题,都不成功,不是卡内存就是卡时间,后缀数组有一招二分后缀,但是各种条件不好写,所以还是憋着复习了一下ac自动机。
学了后缀自动机之后对ac自动机的fail树也更加理解了,一个点的匹配次数其实是fail树上它的子树的匹配次数之和,这和后缀自动机的endpos的原理一模一样。
这道题直接建树,然后跑主串,匹配的时候不用沿着fail回跳,只需在当前节点+1,然后最后按节点深度排序,就是拓扑序列,统计子树和就可以了,对于每个询问输出对应节点的值。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,pos[N];
char s[N*10],t[N];

struct ACAutomaton{
	int tot,tr[N][26],fa[N],dep[N],val[N],a[N],c[N];
	int newnode(){val[++tot]=0;memset(tr[tot],0,sizeof(tr[tot]));return tot;}
	int insert(){
		int n=strlen(t+1),p=0;
		for(int i=1;i<=n;i++){
			if(!tr[p][t[i]-'a']) tr[p][t[i]-'a']=newnode();
			dep[tr[p][t[i]-'a']]=dep[p]+1;
			p=tr[p][t[i]-'a'];
		}
		return p;
	}
	void getfail(){
		queue<int> que;
		for(int i=0;i<26;i++) if(tr[0][i]) que.push(tr[0][i]),fa[tr[0][i]]=0;
		while(!que.empty()){
			int u=que.front();que.pop();
			for(int i=0;i<26;i++)
				if(tr[u][i]) fa[tr[u][i]]=tr[fa[u]][i],que.push(tr[u][i]);
				else tr[u][i]=tr[fa[u]][i]; 
		}
	}
	void solve(){
		int n=strlen(s+1);
		for(int i=1,p=0;i<=n;i++){
			int c=s[i]-'a';
			while(p&&!tr[p][c]) p=fa[p];
			p=tr[p][c];
			if(p) val[p]++;
		}
		for(int i=1;i<N;i++) c[i]=0;
		for(int i=1;i<=tot;i++) c[dep[i]]++;
		for(int i=1;i<N;i++) c[i]+=c[i-1];
		for(int i=tot;i>=1;i--) a[c[dep[i]]--]=i;
		for(int i=tot;i>=1;i--) val[fa[a[i]]]+=val[a[i]];
	}
}ac;

int main(){
	ac.tot=0;memset(ac.tr[0],0,sizeof(ac.tr[0]));
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%s",t+1);
		pos[i]=ac.insert();
	}
	ac.getfail();
	scanf("%s",s+1);
	ac.solve();
	for(int i=1;i<=n;i++) printf("%d\n",ac.val[pos[i]]);
	return 0;
}
posted @ 2020-04-16 12:25  BakaCirno  阅读(112)  评论(0编辑  收藏  举报