• 学习笔记: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;
    } 
    

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

  • 相关阅读:
    maven 的 oracle的Missing artifact com.oracle:******:jar:11.2.0.2.0
    [CF 191C]Fools and Roads[LCA Tarjan算法][LCA 与 RMQ问题的转化][LCA ST算法]
    公司估值(贴现现金流量法DCF)
    Shell编程学习---第五篇:Shell的输入和输出
    S3C2410 实验三——块拷贝、字拷贝(寄存器的理解)
    模板方法模式实现组合查询
    关于方程x^2+y^2=p (p为素数)的解问题
    IOS登陆+注册+抽奖+排行榜
    用PersonalRank实现基于图的推荐算法
    Redis3.0--集群安装部署
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12457428.html
Copyright © 2020-2023  润新知