zoukankan      html  css  js  c++  java
  • 洛谷 P5357 【模板】AC自动机(二次加强版)

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

    原题链接

    Solution

    算法:(AC自动机)

    顾名思义,这是一道 (AC)自动机题目。

    乍一看,诶,这不跟 P3796 【模板】AC自动机(加强版) 差不多吗?

    (加强版)要求输出出现次数最多的模式串,那这个(二次加强版)直接把每个模式串出现次数输出出来不就完了吗?

    怎么感觉还退化了呢QWQ。

    交一发 ———— 于是成功拿到了 76 分的好成绩,其它的都 (TLE) 了。

    让我们来分析一下原因,我们每次是暴力跳 (fail), 那么如果出现这样的串呢 (aaaaa...aaaa),这样相当于要跳 (len) 次。

    究其根本,一条 (fail) 链上的点会被多次修改,时间就浪费在这里了。

    复杂度最坏情况下会退化为 (O(|s| * |t|))(|s|)(|t|) 均表示字符串长度。于是我们的代码就会被卡掉。

    既然暴力跳 (fail)(T),那么我们如何让它不多次修改(对于每个点只修改一次)呢?

    这里介绍两种方法。

    拓扑优化建图

    简单来说,建 (fail) 指针时,更新 (fail) 的入度。

    这样一来,在跳 (fail) 时,将指向当前节点的所有点都更新完之后再更新当前节点,不就可以做到只更新一次了吗。

    由于个人习惯,代码中 (S) 是模式串,(T) 是文本串

    完整代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    
    using namespace std;
    
    const int N = 2e6 + 10;
    int trie[N][27], num[N], vis[N], id[N], fail[N];
    int n, tot;
    int match[N], ans[N], in[N];
    string str, s;
    
    void insert(string s, int id){
    	int now = 0;
    	int len = s.length();
    	for(int i = 0; i < len; i++){
    		int n = s[i] - 'a';
    		if(!trie[now][n])
    			trie[now][n] = ++tot;
    		now = trie[now][n];
    	}
    	match[id] = now;		//标记字符串结尾位置
    }
    
    void getfail(){
    	queue<int> q;
    	for(int i = 0; i < 26; i++)
    		if (trie[0][i])
    			q.push(trie[0][i]);
    	while(!q.empty()){
    		int now = q.front();
    		q.pop();
    		for(int i = 0; i < 26; i++){
    			if(trie[now][i]){
    				fail[trie[now][i]] = trie[fail[now]][i];
    				in[fail[trie[now][i]]]++;			//更新入度
    				q.push(trie[now][i]);
    			}
    			else trie[now][i] = trie[fail[now]][i];
    		}
    	}
    }
    
    void query(string s){
    	int now = 0;
    	for(int i = 0; i < s.size(); i++){
    		now = trie[now][s[i] - 'a'];
    		vis[now]++;			//只需打上标记即可
    	}
    }
    
    void topu(){
    	queue <int> q;
    	for(int i = 1; i <= tot; i++)
    		if(!in[i])
    			q.push(i);
    	while(!q.empty()){
    		int x = q.front();
    		q.pop();
    		int y = fail[x];
    		in[y]--;
    		vis[y] += vis[x];			//向上更新
    		if(!in[y])
    			q.push(y);
    	}
    }
    
    int main(){
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++){
    		cin >> str;
    		insert(str, i);
    	}
    	getfail();
    	cin >> s;
    	query(s);
    	topu();
    	for(int i = 1; i <= n; i++)
    		printf("%d
    ", vis[match[i]]);
    	return 0;
    }
    
    
    建 fail 树

    建出 (fail) 树,直接在上面 (dfs),回溯的时候更新答案。

    由于个人习惯,代码中 (S) 是模式串,(T) 是文本串

    完整代码

    #include <iostream>
    #include <cstring>
    #include <queue>
    #include <vector>
    
    using namespace std;
    
    const int N = 2e5 + 10;
    const int T = 2e6 + 10;
    struct node{
    	int v, nxt;
    }edge[N << 1];
    int head[N], cnt;
    int n;
    char s[N] ,t[T];
    int trie[N][27], tot = 0, fail[N];
    int match[N], vis[N];
    
    void add(int x, int y){			//前向星建图
    	edge[++cnt] = (node){y, head[x]};
    	head[x] = cnt;
    }
    
    void insert(char s[], int id){
    	int len = strlen(s);
    	int now = 0;
    	for(int i = 0; i < len; i++){
    		int x = s[i] - 'a';
    		if(!trie[now][x]) trie[now][x] = ++tot;
    		now = trie[now][x];
    	}
    	match[id] = now;		//标记第i个模式串结尾位置
    }
    
    //AC自动机模板
    void build(){
    	queue <int> q;
    	for(int i = 0; i < 26; i++)
    		if(trie[0][i])
    			q.push(trie[0][i]);
    	while(!q.empty()){
    		int now = q.front();
    		q.pop();
    		for(int i = 0; i < 26; i++){
    			if(trie[now][i]){
    				fail[trie[now][i]] = trie[fail[now]][i];
    				q.push(trie[now][i]);
    			}else trie[now][i] = trie[fail[now]][i];
    		}
    	}
    }
    
    void query(char s[]){
    	int now = 0;
    	int len = strlen(s);
    	for(int i = 0; i < len; i++){
    		now = trie[now][s[i] - 'a'];
    		vis[now]++;					//同理,只打上标记即可
    	}
    }
    
    void dfs(int x){
    	for(int i = head[x]; i; i = edge[i].nxt){
    		dfs(edge[i].v);
    		vis[x] += vis[edge[i].v];		//更新答案
    	}
    }
    
    int main(){
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++){
    		scanf("%s", s);
    		insert(s, i);
    	}
    	scanf("%s", t);
    	build();
    	query(t);
    	for(int i = 1; i <= tot; i++)		//建fail树
    		add(fail[i], i);
    	dfs(0);
    	for(int i = 1; i <= n; i++)
    		printf("%d
    ", vis[match[i]]);	//输出
    	return 0;
    }
    

    End

    本文来自博客园,作者:xixike,转载请注明原文链接:https://www.cnblogs.com/xixike/p/15111334.html

  • 相关阅读:
    Windows API 之 Windows Service
    揭开Socket编程的面纱 (一)
    开发中“错误: 意外地调用了方法或属性访问。” 和 第一行错误 的IE 两个问题( JQ 进行转义字符 , 分页JS 调用 时参数问题。)
    结合MSDN理解windows service 服务安装的三个类。
    VFW基础知识(一些定义性质的。从CSDN中得到的。)
    初次接触WIN FORM,深入事件、委托、方法 ,深入看不到的C#探索。
    C#: +(特性 ) + Attitude C#(类)前面或者(方法)前面 (中括号)定义
    VFW系列教程经典
    依赖注入
    Windows Service:SC 和 InstallUtil 区别
  • 原文地址:https://www.cnblogs.com/xixike/p/15111334.html
Copyright © 2011-2022 走看看