AC自动机学习笔记

AC自动机

trie+kmp

构建fail指针

朴素思想:
当前节点为p,之前已求得fail[p]
trie[p,c]是p 通过字符c指向的 一个子节点,现要求fail[ trie[p,c] ],即fail[x]
若trie[fail[p],c]存在,则fail[x]=trie[fail[p],c], 即p的子节点的fail = p的fail的子节点
否则,让p一直跳fail指针,fail[x]=trie[fail[fail[p]],c],直到存在 / 根节点

fail路径压缩后,可优化为trie图

匹配查询 query

其实就是,在trie上查询s串的每个前缀
在字典树上走一遍s串,每次累加当前后缀能匹配的个数 (通过跳fail实现)

fail树上dfs / 拓扑序优化: O(n)

query_mx时每次都跳fail到根节点,最坏为 O(n*m)
fail树:所有fail指针连成边,是一棵树
即,每个节点的fail指针指向fa,节点0作根
优化:只在fail树的"末端"节点+1(差分思想),再dfs / 按拓扑序转移

例题

P3796 【模板】AC自动机(加强版)
P3808 【模板】AC自动机(简单版)
P5357 【模板】AC自动机(二次加强版)

代码

const int N=150+5; 
int n;
char s[N][75], str[2000010];
int tr[N*70][26],idx;//总字符为 N 
int ed[N*70], fail[N*70];

int id[N*70];//字符串末端的trie节点对应的 id 	( id和trie节点相互映射 ) 
int mp[N*70];//id 映射为字符串末端的 trie节点 

int cnt[N*70];
vector<int> mxi;


namespace AC{
	void insert(char s[],int id){
		int p=0;
		for(int i=0;s[i];++i){
			int ch=s[i]-'a';
			if(tr[p][ch]==0) tr[p][ch]=++idx;
			p=tr[p][ch];
		}
//		ed[p]+=1;
		ed[p]=id;	//1、trie节点映射为模板串id		(前提是不存在相同的模板串) 
		mp[id]=p;	//2、id 映射为 trie节点 		(可以重复相同) 
	}
	
	void build(){	//bfs实现,对trie中每个节点,都逐层求fail指针	(fail指针一定指向层数比自己小的)  
		queue<int> q;
		fu(i,0,25) if(tr[0][i]) q.push(tr[0][i]);
		
		while(q.size()){
			int p=q.front(); q.pop();
			fu(i,0,25){	
				if(tr[p][i]) 					//p的子节点的fail = p的fail的子节点 
					fail[tr[p][i]]=tr[fail[p]][i], q.push(tr[p][i]);
				else tr[p][i]=tr[fail[p]][i];	//若子节点不存在,让p跳fail 
			}
		}
	}
	int query_cnt(char s[]){ //(文本串信息)求多少个模板串 在文本串s中出现过		 
		int p=0, res=0;//
		for(int i=0;s[i];++i){//在trie上走一遍s串 
			p=tr[p][s[i]-'a']; 
			
			for(int j=p; j&&ed[j]!=-1; j=fail[j]) 
				res+=ed[j], ed[j]=-1;	//每个模板串只计数一次,走过的置为-1	(当然置0,每次走到根也可,复杂度较大) 
		}
		return res;
	}
	
	int query_mx(char s[]){//(模板串信息)求在文本串中 出现次数最多的模板串,及其次数 
		int p=0, mx=-1;
		for(int i=0;s[i];++i){
			p=tr[p][s[i]-'a'];
			for(int j=p; j; j=fail[j]) {//这里最坏 O(n*m)复杂度。。可fail树上dfs优化 
				cnt[j]+=1; 
				
				//if(ed[j]==0) continue;					
			}
		}
		
		fu(i,0,idx) if(ed[i]) mx=max(mx,cnt[i]);			//枚举0~idx所有节点 
		fu(i,0,idx) if(ed[i] && cnt[i]==mx) mxi.pb(ed[i]);	//i是trie节点编号,ed[i]才是字符串编号 
		
		return mx;	
	}
	
}

vector<int> g[N*70];
void dp(int x){
	for(auto &y:g[x]){
		dp(y);
		cnt[x]+=cnt[y];
	}
}
int solve(){
	fu(i,0,idx) g[i].clear();
	fu(i,0,idx) if(i!=0) g[fail[i]].pb(i);//fail[i]作fa 
	dp(0); 
	
	int mx=-1;
	fu(i,0,idx) if(ed[i]) mx=max(mx,cnt[i]);
	fu(i,0,idx) if(ed[i] && cnt[i]==mx) mxi.pb(ed[i]);
	return mx;
}
int query_mx_dp(char s[]){//fail树上dfs 
	int p=0;
	for(int i=0;s[i];++i){
		p=tr[p][s[i]-'a'];
		cnt[p]+=1;		//差分思想,节点p到根都 +1 ,dfs求最终贡献 
	}
	return solve();
}

int Work(){
	idx=0;
	mxi.clear();
	fu(i,0,n*70) ms(tr[i],0), ed[i]=fail[i]=0, cnt[i]=0;
	 
	fu(i,1,n) scanf("%s",s[i]), AC::insert(s[i],i);
	
	AC::build();
	
	scanf("%s",str);
//	printf("%d\n", AC::query_cnt(str) );
//	printf("%d\n", AC::query_mx(str) );
	printf("%d\n", query_mx_dp(str));
	
	
	for(auto x:mxi) printf("%s\n",s[x]); 
	return 0;
}
int main(){
	while(~scanf("%d",&n) && n) Work();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值