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;
    } 
    

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

  • 相关阅读:
    队列

    Oracle 12c新特性之——TABLE ACCESS BY INDEX ROWID BATCHED
    连续三月涨势明显,PostgreSQL 将崛起?
    Oracle物理DG自动切换——Dataguard Broker配置
    MSSQL索引视图(indexed view)之简述及使用
    连续三月涨势明显,PostgreSQL 将崛起?
    Scheduler & Task & Worker & Thread & Request & Session & Connection of SQL Server
    MSSQL内存架构及管理
    MSSQL数据库后台进程(线程)
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12457428.html
Copyright © 2011-2022 走看看