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

    这里的(AC)不要理解错啊......
    让一个文本串跑好多模式串的(KMP),全称(Aho-Corasick;automaton)
    很神奇,就像面条机一样
    将线形的字符串改成树形的(Trie)了,将令人懵逼的(next[])数组改为难以理解的失配指针(fail)
    引用大佬(yyb)的话:

    [Trie ext{树的失配指针是指向:沿着其父节点的失配指针,一直向上,直到找到拥有当前这个字母的子节点的节点的那个子节点} ]

    再盗张图吧:

    于是我们可以先建一棵平常的(Trie):
    这里是结构体:

    struct node
    {
    	int kid[28];//对应的儿子节点(a-0,z-25)
    	int end,fail;//分别是有几个子串在此终结,fail指针
    }ac[MAXN];
    

    插入操作(大家都会的)

    void add(char *s)
    {
    	int len=strlen(s),u=0;
    	for(int i=0;i<len;i++)
    	{
    		int j=s[i]-'a';
    		if(!ac[u].kid[j]) ac[u].kid[j]=++cnt;
    		u=ac[u].kid[j];
    	}
    	ac[u].end++;
    }
    

    接下来就是寻找每个节点的失配指针了:
    思路是这样的:
    对于每个节点,枚举所有可能的儿子节点(a--z),如果存在这个节点,就把这个儿子节点的失配指针指向他父亲失配指针对应的儿子节点。
    那你会问了:如果他父亲失配指针对应的节点没有相应的子节点呢?
    (qwq),那是直接自然给成(0)了。
    然后忽然想起(勿喷):

    嗯,如果没有这个子节点,就建一个虚的子节点指向他父亲失配指针对应的儿子节点。
    可以借助队列实现:

    void build() 
    {
    	queue<int> q;
    	int u=0;
    	for(int i=0;i<26;i++)
    	{
    		if(ac[u].kid[i]) ac[ac[u].kid[i]].fail=0,q.push(ac[u].kid[i]);;
    		
    	}
    	while(!q.empty())
    	{
    		u=q.front();
    		q.pop();
    		for(int i=0;i<26;i++)
    		{
    			if(ac[u].kid[i])
    			{
    				ac[ac[u].kid[i]].fail=ac[ac[u].fail].kid[i];
    				q.push(ac[u].kid[i]);
    			}
    			else ac[u].kid[i]=ac[ac[u].fail].kid[i];
    		}
    	}
    	return;
    }
    

    后面,就可以匹配了,实现了真·自动!
    大体就是对于每个节点儿子节点的失配指针跳来跳去就好了。
    注意要将统计了的节点进行标记(习惯将统计结尾数标为-1),以避免重复运算。
    就是这样的:

    int countt(char *s)
    {
    	int len=strlen(s),u=0;
    	for(int i=0;i<len;i++)
    	{
    		u=ac[u].kid[s[i]-'a'];
    		for(int k=u;k&&ac[k].end!=-1;k=ac[k].fail)
    		{
    			ans+=ac[k].end;
    			ac[k].end=-1;
    		}
    	}	
    	return ans;
    }
    

    据我看来,时间复杂度是(O(type(sum len_{ ext{模式串}}+len_{ ext{文本串}})))(这里的(type)代表字符种类数),可以通过本题。
    好了,(AC)自动机的简单教程已经完成了,那就去做板子题吧:
    题目链接:P3808 【模板】AC自动机(简单版)
    对了,这题作为我(AC)的第(300)道题,有一些深层意义呢(你知道的)。
    下面粘上代码:

    (Code):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int MAXN=1000005;
    struct node
    {
    	int kid[28];
    	int end,fail;
    }ac[MAXN];
    int cnt=0,ans=0;
    int n;
    char ch[MAXN];
    void add(char *s)
    {
    	int len=strlen(s),u=0;
    	for(int i=0;i<len;i++)
    	{
    		int j=s[i]-'a';
    		if(!ac[u].kid[j]) ac[u].kid[j]=++cnt;
    		u=ac[u].kid[j];
    	}
    	ac[u].end++;
    }
    void build() 
    {
    	queue<int> q;
    	int u=0;
    	for(int i=0;i<26;i++)
    	{
    		if(ac[u].kid[i]) ac[ac[u].kid[i]].fail=0,q.push(ac[u].kid[i]);;
    		
    	}
    	while(!q.empty())
    	{
    		u=q.front();
    		q.pop();
    		for(int i=0;i<26;i++)
    		{
    			if(ac[u].kid[i])
    			{
    				ac[ac[u].kid[i]].fail=ac[ac[u].fail].kid[i];
    				q.push(ac[u].kid[i]);
    			}
    			else ac[u].kid[i]=ac[ac[u].fail].kid[i];
    		}
    	}
    	return;
    }
    int countt(char *s)
    {
    	int len=strlen(s),u=0;
    	for(int i=0;i<len;i++)
    	{
    		u=ac[u].kid[s[i]-'a'];
    		for(int k=u;k&&ac[k].end!=-1;k=ac[k].fail)
    		{
    			ans+=ac[k].end;
    			ac[k].end=-1;
    		}
    	}	
    	return ans;
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%s",ch);
    		add(ch);
    	}
    	build();
    	scanf("%s",ch);
    	printf("%d
    ",countt(ch));
    	return 0;
    } 
    

    还有两道板题(真多),先咕咕吧,等我慢慢更。

  • 相关阅读:
    一些你可能用到的代码
    iOS 键盘下去的方法
    iOS设计模式汇总
    随笔
    Spring cloud config 分布式配置中心 (三) 总结
    Spring cloud config 分布式配置中心(二) 客户端
    Spring cloud config 分布式配置中心(一) 服务端
    jdbcUrl is required with driverClassName spring boot 2.0版本
    JpaRepository接口找不到 spring boot 项目
    解决IntelliJ “Initialization failed for 'https://start.spring.io'
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12457428.html
Copyright © 2011-2022 走看看