AC自动机

AC自动机

爆肝计划✪ ω ✪

我搞不懂AC自动机是什么?

大佬对我说:

自动机=tiri树+kmp;

我求大佬再详细点,

他说你弄懂了,还是这个式子。

w(゚Д゚)w ヾ(•ω•`)o

(蒟蒻) (大佬)

在这里先发一个大佬的连接吧。[洛谷日报第44期]强势图解AC自动机,在这里我就详细讲一下过程

  1. 应用方向:

    举一个常见的例子吧:

    就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过

  2. 当时,当提出kmp思想时,我只是单纯认为直接把kmp的模板拷过去就可以了,然而事情并没有这么简单

着重介绍的是kmp的思维,并非代码(当时我还傻乎乎的从模板里找kmp的影子?)。

  1. 那么就开始讲过程吧。

    (字丑,见谅)

    如果对这图感兴趣可以点击这里→字典树学习

那我们开始吧

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上是Fail形成和ac自动机寻找过程’

那么上这题基础模板吧

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000005

int trie[maxn][256]; //字典树
int cntword[maxn];  //记录该单词出现次数
int fail[maxn];     //失败时的回溯指针
int cnt = 0;

void insertWords(string s){
   int root = 0;
   for(int i=0;i<s.size();i++){
       int next = s[i] - 'a';
       if(!trie[root][next])
           trie[root][next] = ++cnt;
       root = trie[root][next];
   }
   cntword[root]++;      //当前节点单词数+1
}
void getFail(){
   queue <int>q;
   for(int i=0;i<26;i++){      //将第二层所有出现了的字母扔进队列
       if(trie[0][i]){
           fail[trie[0][i]] = 0;
           q.push(trie[0][i]);
       }
   }

//fail[now]    ->当前节点now的失败指针指向的地方
tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
   while(!q.empty()){
       int now = q.front();
       q.pop();

       for(int i=0;i<26;i++){      //查询26个字母
           if(trie[now][i]){
               //如果有这个子节点为字母i+'a',则
//让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的下一个节点)
               //有点绕,为了方便理解特意加了括号

               fail[trie[now][i]] = trie[fail[now]][i];
               q.push(trie[now][i]);
           }
           else//否则就让当前节点的这个子节点
               //指向当前节点fail指针的这个子节点
               trie[now][i] = trie[fail[now]][i];
       }
   }
}


int query(string s){
   int now = 0,ans = 0;
   for(int i=0;i<s.size();i++){    //遍历文本串
       now = trie[now][s[i]-'a'];  //从s[i]点开始寻找
       for(int j=now;j && cntword[j]!=-1;j=fail[j]){
           //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
           ans += cntword[j];
           cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
       }
   }
   return ans;
}

int main() {
   int n;
   string s;
   cin >> n;
   for(int i=0;i<n;i++){
       cin >> s ;
       insertWords(s);
   }
   fail[0] = 0;
   getFail();
   cin >> s ;
   cout << query(s) << endl;
   return 0;
}

不多说,来个病毒二连击( ̄y▽, ̄)╭

病毒侵袭 -HDU2896

Problem Description

当太阳的光辉逐渐被月亮遮蔽,世界失去了光明,大地迎来最黑暗的时刻。。。。在这样的时刻,人们却异常兴奋——我们能在有生之年看到500年一遇的世界奇观,那是多么幸福的事儿啊~~
但网路上总有那么些网站,开始借着民众的好奇心,打着介绍日食的旗号,大肆传播病毒。小t不幸成为受害者之一。小t如此生气,他决定要把世界上所有带病毒的网站都找出来。当然,谁都知道这是不可能的。小t却执意要完成这不能的任务,他说:“子子孙孙无穷匮也!”(愚公后继有人了)。
万事开头难,小t收集了好多病毒的特征码,又收集了一批诡异网站的源码,他想知道这些网站中哪些是有病毒的,又是带了怎样的病毒呢?顺便还想知道他到底收集了多少带病毒的网站。这时候他却不知道何从下手了。所以想请大家帮帮忙。小t又是个急性子哦,所以解决问题越快越好哦~~

Input

第一行,一个整数N(1<=N<=500),表示病毒特征码的个数。
接下来N行,每行表示一个病毒特征码,特征码字符串长度在20—200之间。
每个病毒都有一个编号,依此为1—N。
不同编号的病毒特征码不会相同。
在这之后一行,有一个整数M(1<=M<=1000),表示网站数。
接下来M行,每行表示一个网站源码,源码字符串长度在7000—10000之间。
每个网站都有一个编号,依此为1—M。
以上字符串中字符都是ASCII码可见字符(不包括回车)。

Output

依次按如下格式输出按网站编号从小到大输出,带病毒的网站编号和包含病毒编号,每行一个含毒网站信息。
web 网站编号: 病毒编号 病毒编号 …
冒号后有一个空格,病毒编号按从小到大排列,两个病毒编号之间用一个空格隔开,如果一个网站包含病毒,病毒数不会超过3个。
最后一行输出统计信息,如下格式
total: 带病毒网站数
冒号后有一个空格。

Sample Input

3
aaa
bbb
ccc
2
aaabbbccc
bbaacc

Sample Output

web 1: 1 2 3
total: 1

这是一道ac自动机基础题,仔细一读肯定会发现并没有太多改动的地方,我们要做的是给依次标记,输出即可。

#include <cstdio>
#include <iostream>
#include <string.h>
#include <string>
#include <queue>
#include <algorithm>
using namespace std;
const int maxk=128,maxn=10005;
char s[maxn],a[maxk*2];
struct node {
	struct node *fail;
	struct node *nxt[maxk];
	int cnt,id;
	
	void init() {
		for (int i=0;i<maxk;i++) nxt[i]=NULL;
		fail=NULL;
		cnt=id=0;
	}
};
 
void insert(string s,node *root,int len,int id) {
	node *p=root;
	int i;
	for (i=0;i<len;i++) {
		int pos=s[i];
		if (p->nxt[pos]==NULL) {
			p->nxt[pos]=new node;
			p->nxt[pos]->init();
		}
		p=p->nxt[pos];
	}
	p->cnt++;
	p->id=id;
}
 
void buildfail(node *root) {
	node *p=root;
	node *now;
	int front=0,tail=0,i;
	queue<node *> q;
	for (i=0;i<maxk;i++) {
		if (p->nxt[i]!=NULL) {
			p->nxt[i]->fail=root;
			q.push(p->nxt[i]);
		} else p->nxt[i]=root;
	}
	while (!q.empty()) {
		now=q.front();
		q.pop();
		for (i=0;i<maxk;i++) {
	    	if (now->nxt[i]!=NULL) {
		    	now->nxt[i]->fail=now->fail->nxt[i];
		    	q.push(now->nxt[i]);
	    	} else now->nxt[i]=now->fail->nxt[i];
    	}
	}
}
 
bool search(string s,node *root,int len,int cas) {
	int k=0,i;
	node *q[3];
	node *p=root,*now;
	for (i=0;i<len;i++) {
		p=p->nxt[s[i]];
		now=p;
		while (now!=root&&now->cnt!=-1) {
			if (now->cnt) {
				q[k++]=now;
				now->cnt=-1;
			}
			now=now->fail;
		}
 	}
 	if (k) {
 		printf("web %d:",cas);
 		int id[3];
 		for (i=0;i<k;i++) id[i]=q[i]->id;
 		sort(id,id+k);
 		for (i=0;i<k;i++) {
 			printf(" %d",id[i]);
 			q[i]->cnt=1;
 		}
 		printf("\n");
 		return true;
 	}
	return false;
}
 
int main() {
	int n,len,i,j,tot=0;
	node *root=new node;
	root->init();
	scanf("%d",&n);
	for (i=1;i<=n;i++) {
		scanf("%s",a);
		insert(a,root,strlen(a),i);
	}
	buildfail(root);
	scanf("%d",&n);
	for (i=1;i<=n;i++) {
		scanf("%s",s);
		tot+=search(s,root,strlen(s),i);
	}
	printf("total: %d\n",tot);
	return 0;
}

第二道

病毒侵袭持续中 HDU3065

Problem Description

小t非常感谢大家帮忙解决了他的上一个问题。然而病毒侵袭持续中。在小t的不懈努力下,他发现了网路中的“万恶之源”。这是一个庞大的病毒网站,他有着好多好多的病毒,但是这个网站包含的病毒很奇怪,这些病毒的特征码很短,而且只包含“英文大写字符”。当然小t好想好想为民除害,但是小t从来不打没有准备的战争。知己知彼,百战不殆,小t首先要做的是知道这个病毒网站特征:包含多少不同的病毒,每种病毒出现了多少次。大家能再帮帮他吗?

Input

第一行,一个整数N(1<=N<=1000),表示病毒特征码的个数。
接下来N行,每行表示一个病毒特征码,特征码字符串长度在1—50之间,并且只包含“英文大写字符”。任意两个病毒特征码,不会完全相同。
在这之后一行,表示“万恶之源”网站源码,源码字符串长度在2000000之内。字符串中字符都是ASCII码可见字符(不包括回车)。

Output

按以下格式每行一个,输出每个病毒出现次数。未出现的病毒不需要输出。
病毒特征码: 出现次数
冒号后有一个空格,按病毒特征码的输入顺序进行输出。

Sample Input

3
AA
BB
CC
ooxxCC%dAAAoen....END

Sample Output

AA: 2
CC: 1

在这里,我们要建一个结构体,里面存储该病毒的次序,特征,次数,然后在查询中记录次数,最后依次把不为0的数挑出来输出即可。同时注意题中说明是大写字母,那么可以将不是大写字母的字符去掉,在进行查找。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
#define son_num 26
#define maxn 2000005
struct nodev //创建病毒结构体
{
    char name[55]; //病毒名字
    int num; //病毒出现的次数
}virus[1010];
char web[maxn];
struct node
{
    int code;
    int terminal;
    node *fail;
    node *next[son_num];
    node()
    {
        fail=NULL;
        code=0;
        terminal=0;
        memset(next,NULL,sizeof(next));
    }
};
node *que[maxn];
//构建Tire树
void insert(node *root,char *str,int x) //x为该病毒的编号
{
    node *p=root;
    int i=0,index;
    while(str[i])
    {
        index=str[i]-'A';
        if(p->next[index]==NULL)
          p->next[index]=new node();
        p=p->next[index];
        i++;
    }
    p->code=x;
    p->terminal=1;
}
//寻找失败指针
void build_fail(node *root)
{
    int head=0,tail=0;
    root->fail=NULL;
    que[head++]=root;
    while(head!=tail)
    {
        node *temp=que[tail++];
        node *p=NULL;
        for(int i=0;i<son_num;i++)
        {
            if(temp->next[i]!=NULL)
            {
                if(temp==root) temp->next[i]->fail=root;
                else{
                p=temp->fail;
                while(p!=NULL)
                {
                    if(p->next[i]!=NULL)
                    {
                        temp->next[i]->fail=p->next[i];
                        break;
                    }
                    p=p->fail;
                }
                if(p==NULL)
                  temp->next[i]->fail=root;}
                que[head++]=temp->next[i];
            }
        }
    }
}
//询问主串中含有多少个关键字
void query(node *root,char *str)
{
    int i=0,cnt=0,index,len;
    len=strlen(str);
    node *p=root;
    while(str[i])
    {
        if(isupper(str[i])) //判断当前字符是否为大写字母
        {
            index=str[i]-'A';
            while(p->next[index]==NULL&&p!=root)
              p=p->fail;
            p=p->next[index];
            if(p==NULL) p=root;
            node *temp=p;
            while(temp!=root&&temp->code)
            {
                virus[temp->code].num+=temp->terminal;
                temp=temp->fail;
            }
        }
        else p=root; //如果不是大写字母,则指向树根
        i++;
    }
}
int main()
{
    int n;
    while(scanf("%d",&n)!=-1)
    {
        node *root=new node();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",virus[i].name);
            virus[i].num=0;
            insert(root,virus[i].name,i);
        }
        build_fail(root);
        scanf("%s",web);
        query(root,web);
        for(int i=1;i<=n;i++)
          if(virus[i].num)  //输出出现过的病毒特征码
            printf("%s: %d\n",virus[i].name,virus[i].num);
    }
    return 0;
}

AC自动机真有意思≡(▔﹏▔)≡

令人头大。。。

有不准确的留言呦(* ̄3 ̄)╭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值