zoukankan      html  css  js  c++  java
  • AC自动机学习笔记

    再不更新这里都长草了

    前置知识

    • KMP
    • Trie树

    前言

    AC自动机是一个字符串匹配算法,与KMP的区别在于,AC自动机可以用(O(sum |s_i| + |S|))的复杂度在文本串中同时查找多个模式串,例如这道题

    对于初学者 就是我 而言,可以简单地将AC自动机理解为KMP + Trie树,整个算法分为三步:

    1. 将所有的模式串建成一颗Trie树
    2. 求出Trie树上每一个节点的失配指针(fail)(类似KMP的next)
    3. 将文本串在Trie树上进行匹配

    算法过程

    建立Trie

    这一步和普通的字典树一样,不解释

    void insert(string& s) {
    	int p = 0;
    	for(int i = 0; i < s.length(); i++) {
    		if(t[p][s[i] - 'a'] == 0) {
    			t[p][s[i] - 'a'] = ++tot;
    		}
    		p = t[p][s[i] - 'a'];
    	}
    	num[p]++;
    }
    

    求失配指针

    (fail_u)的定义为,Tire树内所有字符串的所有前缀中,后缀匹配长度最大的的位置;如下图中,(fail_i = j)

    (图中不同颜色的边代表字符)

    fail指针

    如图,节点(i)与节点(j)的后缀匹配最长,所以(fail_i = j)

    fail指针

    如图,(fail_i = j),则(i)的粉色子节点(此处用颜色表示字符)必然也与(j)的粉色子节点有最长的后缀,因此该子节点的(fail)指针就是(k)

    具体求法:对于(u)的子节点(v),其(fail)指针为(u)(fail)指针的字符与(v)相同的子节点,证明很简单,因为(fail_u)(u)的后缀匹配最长,而(v)节点上的字符(i)显然与(trie[fail_u][i])上的字符(i)相同,所以(fail_v = trie[fail_u][i])

    理解原理后我们很容易就能求出所有的(fail)指针:在字典树上跑一遍BFS,根节点的子节点的(fail)指针显然为0,所以先将这些点放入队列;

    对于每一个当前搜索到的节点(u),它的(fail)指针肯定已经被求出,所以我们只需要枚举它的所有子节点t[u][i],建立(fail)指针 fail[t[u][i]] = t[fail[u]][i]

    代码如下:

    void getFail() { //bfs构建fail数组
    	queue<int> q;
    	
    	for(int i = 0; i < 26; i++) { //根结点下面直接连的第一层结点,fail直接指向根结点0 
    		if(t[0][i]) {
    			q.push(t[0][i]);
    		}
    	}
    	while(!q.empty()) { //队列中维护能够拓展fail值的结点 
    		int u = q.front(); q.pop();
    		
    		for(int i = 0; i < 26; i++) {
    			if(t[u][i]) {
                    //失配时,以trie[u][i]结尾的后缀尽量在trie中找一个与之相同的前缀(类似KMP)
    				fail[t[u][i]] = t[fail[u]][i];
    				q.push(t[u][i]);
    			} else {
                    //节点不存在,往上连,最多回到根结点0, 注意是trie不是fail数组
    				t[u][i] = t[fail[u]][i];
    			}
    		}
    	}
    }
    

    字符串匹配

    这一部分理解难度不大,看代码注释即可:

    int query(string s) {
    	int p = 0, res = 0; //p表示trie中的结点,res表示总和
    	
    	for(int i = 0; i < s.length(); i++) {
    		p = t[p][s[i] - 'a']; //获取当前节点
    		
    		for(int j = p; j != 0 && num[j] != -1; j = fail[j]) { //从p开始一直往上跳
    			res += num[j]; //num[j]表示以当前位置结尾字符串的总数,它们肯定都是
    			num[j] = -1; //避免重复计算
    		}
    	}
    	return res;
    }
    

    (p)表示将字符串在(Trie)树上匹配时的位置,在每一轮循环中以(p)为起点向上跳,寻找匹配即可

    复杂度分析

    Trie树的大小为(sum |s_i|),建立(fail)指针的复杂度与其相同,遍历文本串需要(O(|S|)),总时间复杂度为(O(sum |s_i| + |S|))

    参考资料

    AC 自动机 - OI Wiki

  • 相关阅读:
    sql函数
    sql日期
    Windows下串口编程
    Libreoffice/Office:禁止首字母自动大写功能
    convert:图片转pdf失败
    LibreOffice/Calc:单元格设置下拉菜单
    Ubuntu:查询计算机软硬件信息
    tar:文件打包归档
    中科大自主招生2018年笔试数学之五
    文件分割与合并
  • 原文地址:https://www.cnblogs.com/Helium-Air/p/ac-automaton-note.html
Copyright © 2011-2022 走看看