• AC自动机总结


    AC自动机:用来解决多模式串匹配问题。

    模式串:要寻找的那个字符串。
    AC自动机的本质可以看作KMP+Trie树。KMP构造next数组,目的是为了在每次失配过后,都找到当前仍能匹配的最长长度,针对一个模式串。AC自动机则是对于多个模式串来说,每次失配都找到所有模式串中,还能匹配的最长的部分。在实现方面,通过fail失配指针来实现。
    fail指针指向的是当前点代表的前缀子串在所有模式串中的最长后缀。

    构造方法:
    1.对所有模式串建立字典树。
    2.BFS求出fail指针。比较关键的部分是fail[v] = tr[fail[u]][i];这一行代码的意思是,设当前结点的字符为i,则当前结点的fail指针指向其父亲的fail指针的i儿子。
    另有一个跳边的优化:若i儿子不存在,tr[u][i] = tr[fail[u]][i];,则直接将该节点的i儿子设为fail指针的i儿子。

    P3808 【模板】AC自动机(简单版)
    给定(n)个模式串(s_{i})和一个文本串(t),求有多少个不同的模式串在文本串里出现过。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN = 1000005;
    
    inline int read(){
    	int k = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
    	return k * f;
    }
    
    
    int tr[MAXN][26], val[MAXN], fail[MAXN], cnt;
    char s[MAXN];
    
    void insert(char *str){
    	int u = 0;
    	for(int i = 0; str[i] != ''; i++){
    		if(tr[u][str[i] - 'a'] == 0){
    			tr[u][str[i] - 'a'] = ++cnt;
    		}
    		u = tr[u][str[i] - 'a'];
    	}
    	val[u]++;
    }
    
    void bfs(int u){
    	queue <int> q;
    	for(int i = 0; i < 26; i++){
    		if(tr[0][i]){
    			fail[tr[0][i]] = 0;
    			q.push(tr[0][i]);
    		}
    	}
    	while(!q.empty()){
    		u = q.front(); q.pop();
    //		printf("u = %d
    ", u);
    		for(int i = 0; i < 26; i++){
    			int v = tr[u][i];
    			if(v){
    				fail[v] = tr[fail[u]][i];
    				q.push(v);
    //				printf("v = %d, fail = %d
    ", v, fail[v]);
    			}
    			else{ //youhua
    				tr[u][i] = tr[fail[u]][i];
    			}
    		}
    	}
    }
    
    int query(char *str){
    	int u = 0, ret = 0;
    	for(int i = 0; str[i] != ''; i++){
    		u = tr[u][str[i] - 'a'];
    //		ret += val[u];
    		//跳边寻找所有满足条件的模式串 
    		for(int t = u; t != 0 && val[t] != -1; t = fail[t]){
    			ret += val[t];
    			val[t] = -1;
    		}
    	}
    	return ret;
    }
    
    int main(){
    	int n = read();
    	for(int i = 1; i <= n; i++){
    		scanf("%s", s);
    		insert(s);
    	}
    	
    //	printf("cnt = %d
    ", cnt);
    	bfs(0);
    //	for(int i = 1; i <= cnt; i++){
    //		printf("fail[%d] = %d
    ", i, fail[i]);
    //	}
    	
    	
    	scanf("%s", s);
    	int ans = query(s);
    	printf("%d", ans);
    	return 0;
    }
    

    P3796 【模板】AC自动机(加强版)
    同简单版,唯一不同的地方在于统计的方法不一样。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN = 1000005;
    
    inline int read(){
    	int k = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
    	return k * f;
    }
    
    
    int tr[MAXN][26], val[MAXN], fail[MAXN], cnt;
    int tim[1000];
    char s[200][100];
    char ch[MAXN];
    
    void insert(char *str, int num){
    	int u = 0;
    	for(int i = 0; str[i] != ''; i++){
    		if(tr[u][str[i] - 'a'] == 0){
    //			printf("cnt = %d
    ", cnt);
    //			printf("%c, str[i]", str[i]);
    			tr[u][str[i] - 'a'] = ++cnt;
    		}
    		u = tr[u][str[i] - 'a'];
    	}
    	val[u] = num;
    }
    
    void bfs(int u){
    	queue <int> q;
    	for(int i = 0; i < 26; i++){
    		if(tr[0][i]){
    			fail[tr[0][i]] = 0;
    			q.push(tr[0][i]);
    		}
    	}
    	while(!q.empty()){
    		u = q.front(); q.pop();
    		for(int i = 0; i < 26; i++){
    			int v = tr[u][i];
    			if(v){
    				fail[v] = tr[fail[u]][i];
    				q.push(v);
    			}
    			else{ //youhua
    				tr[u][i] = tr[fail[u]][i];
    			}
    		}
    	}
    }
    
    void query(char *str){
    	int u = 0, ret = 0;
    	for(int i = 0; str[i] != ''; i++){
    		u = tr[u][str[i] - 'a'];
    		for(int t = u; t != 0; t = fail[t]){
    			if(val[t]){
    				tim[val[t]]++;
    			}
    		}
    	}
    	return;
    }
    
    int main(){
    	
    	while(1){
    		int n = read();
    		if(n == 0) break;
    		cnt = 0;
    		memset(tim, 0, sizeof(tim));
    		memset(tr, 0, sizeof(tr));
    		memset(val, 0, sizeof(val));
    		memset(fail, 0, sizeof(fail));
    		for(int i = 1; i <= n; i++){
    			scanf("%s", s[i]);
    //			printf("%s
    ", s[i]);
    			insert(s[i], i);
    		}
    		bfs(0);
    		scanf("%s", ch);
    //		printf("ch = %s
    ", ch);
    		query(ch);
    		int t = 1;
    		for(int i = 1; i <= n; i++){
    			if(tim[t] < tim[i]){
    				t = i;
    			}
    		}
    		printf("%d
    ", tim[t]);
    		for(int i = 1; i <= n; i++){
    			if(tim[t] == tim[i]){
    				printf("%s
    ", s[i]);
    			}
    		}
    		
    	}
    	return 0;
    }
    

    P5357 【模板】AC自动机(二次加强版)
    不同点在于可能会出现相同的模式串。这样的话暴力跳fail匹配复杂度最高可以到(n^2)可能会被卡。
    解决方法:可以发现每个结点的fail边构成了一颗树。由于每次跳fail边都会跳到不能再跳为止,因此每次到一个点,都会对该点到根的路径上每一个点有贡献。因此采用拓扑排序,从叶结点开始累加答案,这样避免了暴力跳边。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN = 2000005;
    
    inline int read(){
    	int k = 0, f = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
    	return k * f;
    }
    
    
    int tr[MAXN][26], val[MAXN], fail[MAXN], cnt;
    int tim[MAXN], pos[MAXN];
    //int flag[MAXN];
    int du[MAXN];
    char s[MAXN];
    int vis[MAXN];
    queue <int> q;
    
    void insert(char *str, int num){
    	int u = 0;
    	for(int i = 0; str[i] != ''; i++){
    		if(tr[u][str[i] - 'a'] == 0){
    			tr[u][str[i] - 'a'] = ++cnt;
    		}
    		u = tr[u][str[i] - 'a'];
    	}
    	pos[num] = u;
    	if(!val[u]){
    		val[u] = num;
    	}
    }
    
    void bfs(int u){
    	while(!q.empty()) q.pop();
    	for(int i = 0; i < 26; i++){
    		if(tr[0][i]){
    			fail[tr[0][i]] = 0;
    			q.push(tr[0][i]);
    		}
    	}
    	while(!q.empty()){
    		u = q.front(); q.pop();
    		for(int i = 0; i < 26; i++){
    			int v = tr[u][i];
    			if(v){
    				fail[v] = tr[fail[u]][i];
    				du[fail[v]]++;//
    				q.push(v);
    			}
    			else{ //youhua
    				tr[u][i] = tr[fail[u]][i];
    			}
    		}
    	}
    }
    
    void query(char *str){
    	int u = 0;
    	for(int i = 0; str[i] != ''; i++){
    		u = tr[u][str[i] - 'a'];
    		tim[u]++;
    	}
    	return;
    }
    
    int main(){
    //	freopen("out.txt", "r", stdin);
    //	freopen("out1.txt", "w", stdout);
    	int n = read();
    	for(int i = 1; i <= n; i++){
    		scanf("%s", s);
    		insert(s, i);
    	}
    	bfs(0);
    	
    	scanf("%s", s);
    	query(s);
    	
    //	for(int i = 1; i <= n; i++){
    //		int u = pos[i];
    //		if(flag[u]) continue;
    //		for(int t = fail[u]; t != 0; t = fail[t]){
    //			if(val[t]){
    //				du[t]++;
    //				break;
    //			}
    //		}
    //		flag[u] = 1;
    //	}
    //	for(int i = 1; i <= cnt; i++){
    //		printf("du = %d
    ", du[i]);
    //	}
    	while(!q.empty()) q.pop();
    	for(int i = 1; i <= cnt; i++){
    		if(du[i] == 0){
    			q.push(i);
    		}
    	}
    	
    	while(!q.empty()){
    		int u = q.front(); q.pop();
    		int v = fail[u];
    		tim[v] += tim[u];
    //		vis[val[u]] = tim[u];
    		du[v]--;
    		if(du[v] == 0){
    			q.push(v);
    		}
    	}
    	
    	for(int i = 1; i <= n; i++){
    		printf("%d
    ", tim[pos[i]]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Windows Embedded CE 中断结构分析
    linux framebuff驱动总结
    Linux assemblers: A comparison of GAS and NASM
    使用C#编写ICE分布式应用程序
    makefile的写法
    在客户端中如何定位服务器(即如何寻找代理)
    番茄花园洪磊: 微软很早给我发过律师函
    利用ICE编写程序的几个注意点
    ICE架构
    AT&T汇编语言与GCC内嵌汇编简介
  • 原文地址:https://www.cnblogs.com/asdf1229/p/15075812.html
Copyright © 2020-2023  润新知