zoukankan      html  css  js  c++  java
  • AC自动机--速成版

    没办法,学了又忘了,暑假上课没听懂的锅现在要补......

    AC自动机的思想:

    Trie树 + KMP,但是有人称它为"树上KMP",我觉得也蛮形象的。

    确实也是这样子的,AC自动机主要就是运用了KMP类似的思想以及操作:"失配指针"(字符串匹配都是运用已知信息来减少无效匹配次数......)

    同时AC自动机支持多个模式串的匹配,这样子就运用到了Trie树。

    (Fail)指针

    (Fail)指针的含义:最长的 当前字符串的后缀 在Trie树上的编号

    如果一个点(i)(Fail)指针指向(j)。那么根节点到(j)的字符串是根节点到(i)的字符串的一个后缀。
    以下面的图为例,我们肉眼就可以发现,最左下的那个"d"的(Fail)指针应该指向中间最下面那个"d",因为"abcd"的后缀集就有"bcd"这一元素

    具体实现((Fail指针的构建方法))

    我们明确一点,我们是用Trie树上BFS的方式来建立(Fail)指针.

    • 首先我们把模式串都丢到Trie上面去

    BMktED.png

    (我们默认叶子节点是每个模式串的最后一个字符(真实情况未必是这样!))

    ps.上面这张图借用的是luogu用户hicc0305

    显然,每一个点(i)(Fail)指针指向的点的深度一定是比(i)小的(因为是后缀)。

    同时,我们要使得(i)(Fail)指针指向的位置尽量的深。

    分情况讨论:

    • 如果(i)的父亲节点不是真正的存在(i)这个子节点(也就是没有真实出现在模式串中,但是我们仍然要遍历)

    也就是说比如串"abcb"

    我们的第3个字符是c,在第三个字符这一层我们仍然要遍历"a , b , c , d , e , f , g "等等....直到"z",并且为它们建立(Fail)指针,但是显然只有"c"是真正在文本串中的。

    那么对于例如上面的"a,b,d,e,f,g"等等都是并非"真实存在"的,就把当前节点的子节点指向(Fail_{fa})节点的具有相同的值的子节点。

    • 如果 (i)的父亲节点确确实实的存在(i)这个子节点(出现在上图中的样子,也就是确确实实出现在一个模式串中的)

    不妨令(i)父亲(Fail)指针为(Fail_{fa})

    (i)(Fail)指针指向的点为(Fail_i),那么我们就会令(Fail_i)等于(Fail_{fa})的儿子节点中与(i)值相同的那个点.

    很多人没有介绍这里,蒟蒻我看了十分钟才明白为什么(Fail_{fa})的儿子节点中 一定 会有一个点与(i)值相同

    首先(i)节点的父亲节点一定是深度比它浅的,那么父亲节点的(Fail)指针又比父亲节点浅。这样子(Fail_{fa})至少比(i)的深度少了2。

    (Fail_{fa})的儿子节点中的与(i)值相同的节点至少深度比(i)少1,满足(Fail)指针的条件。

    根据BFS,我们默认(i)上层的一定是已经构造好了(Fail)指针的了,而且根据构造方法,对于任何一个文本种类

    我们都会遍历,就如同上面说的"abcb"的例子中,遍历第三层的时候我们不仅仅帮"c"建立了(Fail)指针,我们还帮"a,b,d,e,f,g...x,y,z"都建立了(Fail)指针,所以只要是深度比(i)小的节点中,必定有一个节点跟(i)的值相同!

    对于构造方法的例子:

    我们以图上最左下那个"d"为例,我们假设遍历到了这个节点,因为是BFS,我们默认它上层所有节点已经建成了

    (Fail) 指针。这里它的父亲节点"c"的(Fail)指针显然应该指向最中间的"c"节点,那么我们根据构造方法,

    (Fail_i) 等于(Fail_{fa})的儿子节点中与(i)值相同的那个点,也就是最中间的"c"的子节点"d"。

    (Fail)指针的构建到此结束.按照上面两种情况即可建成(Fail)指针,代码也很好写呀!

    康康代码吧

    Code

    #include <bits/stdc++.h>
    using namespace std;
    struct {
    	int end,Fail;
    	int son[26];
    }AC[1000005];
    char a[1000005],s[1000005];
    int cnt = 0,n;
    int vis[1000005];
    void build()
    {
    	int len = strlen(s),now = 0;
    	for(int i = 0 ; i < len ; i ++)
    	{
    		int num = s[i] - 'a';
    		if(! AC[now].son[num])
    			AC[now].son[num] =++cnt;
    		now = AC[now].son[num];
    	}
    	AC[now].end ++;
    }
    
    void GetFail()
    {
    	int now = 0,head = 0,tail = 0;
    	for(int i = 0 ; i < 26 ; i ++)
    		if(AC[0].son[i] != 0)
    			tail ++ , vis[tail] = AC[0].son[i];
    	//第一层的Fail指针只能指向Root(这里是0号节点),优先进入队列
    	while(head < tail)
    	{
    		head ++;
    		int v = vis[head];
    		int Fail = AC[v].Fail;//这个点的Fail指针 
    		for(int i = 0 ; i < 26 ; i ++)//遍历当前点的儿子节点
    		{
    			if(AC[v].son[i])//如果"真实存在这个点"
    			{
    				AC[AC[v].son[i]].Fail = AC[Fail].son[i];
    				tail ++;
    				vis[tail] = AC[v].son[i];//记得入队
    			}
    			else AC[v].son[i] = AC[Fail].son[i];
    			//否则就把这个儿子节点接到Fail[fa]的相同值的儿子节点上
    		}
    	}
    	return ;//这就完事了......
    }
    
    void GetAns()
    {
    	int len = strlen(a),now = 0 , ans = 0;
    	for(int i = 0 ; i < len  ; i ++)
    	{
    		int num = a[i] - 'a';
    		now = AC[now].son[num];
    		for(int u = now ; AC[u].end != -1 && u ; u = AC[u].Fail)
                      //u == 0 表示访问到了一个虚节点,那么匹配失败,AC[u] == -1表示当前已经匹配过了,就跳走
    		{
    			ans += AC[u].end;
    			AC[u].end = -1;
    		}
    	}
    	cout << ans << endl;
    	return ;
    }
    
    int main()
    {
    	cin >> n;
    	for(int i = 1 ; i <= n ; i ++)
    	{
    		cin >> s;
    		build();
    	}
    	GetFail();
    	cin >> a;
    	GetAns();
    	return 0;
    }
    

    AC自动机加强版:

    给你n个模式串以及1个文本串,你需要求出哪个模式串在文本串中出现次数最多,输出最多出现的次数以及出现次数最多的模式串.

    思路

    修改一下end存的东西以及查询答案的方式就行了(char数组开小了居然没有RE而是输出超限?不知道输出了些啥(数组越界居然会影响这么多东西)......)

    #include <bits/stdc++.h>
    using namespace std;
    int n;
    int cnt = 0,coun[500],T = 0;
    char s[1505];
    char t[1500005];
    char ans[155][75];
    int vis[1000005];
    #define Faili AC[AC[v].son[i]].Fail
    struct {
    	int end,Fail;
    	int son[26];
    	void clea()
    	{
    		end = Fail = 0;
    		for(int i = 0 ; i < 26 ; i ++)
    			son[i] = 0;
    	}
    }AC[1000005];
    
    void build(int k)
    {
    	int len = strlen(s) , now  = 0;
    	for(int i = 0 ; i < len ; i ++)
    	{
    		int num = s[i] - 'a';
    		if(AC[now].son[num] == 0)
    			cnt ++ , AC[now].son[num] = cnt;
    		now = AC[now].son[num];
    		ans[k][i] = s[i];
    	}
    	AC[now].end = k;
    	return ;
    }
    
    void GetFail()
    {
    	int head = 0 , tail = 0 , now = 0;
    	for(int i = 0 ; i < 26 ; i ++)
    		if(AC[0].son[i])tail++,vis[tail] = AC[0].son[i];
    	while(head < tail)
    	{
    		head++;
    		int v = vis[head];
    		int Failfa = AC[v].Fail;
    		for(int i = 0 ; i < 26 ; i ++)
    		{
    			if(AC[v].son[i])
    			{
    				Faili = AC[Failfa].son[i];
    				tail ++;
    				vis[tail] = AC[v].son[i];
    			}
    			else AC[v].son[i] = AC[Failfa].son[i];
    		}
    	}
    	return ;
    }
    
    void clean()
    {
    	for(int i = 0 ; i <= cnt ; i ++)
    		AC[i].clea();
    	for(int i = 1 ; i <= T ; i ++)
    	{
    		coun[i] = 0;
    		memset(ans[i],0,sizeof(ans[i]));
    	}
    	cnt = 0;T = 0;
    }
    
    void Compare()
    {
    	int len = strlen(t) , now = 0;
    	for(int i = 0 ; i < len  ;i ++)
    	{
    		int num = t[i] - 'a';
    		now = AC[now].son[num];
    		int u = now;
    		for(; u ; u = AC[u].Fail)
    		{
    			coun[AC[u].end]++;
    		}
    	}
    }
    
    int main()
    {
    	while(1)
    	{
    		cin >> n;
    		if(n == 0)break;
    		clean();
    		
    		while(n)
    		{
    			cin >> s;
    			T++;
    			build(T);
    			memcpy(ans[T],s,sizeof(s));
    			n--;
    		}
    		
    		GetFail();
    		cin >> t;
    		Compare();
    		int M = 0;
    		for(int i = 1 ; i <= T ; i ++)
    			M = max(M,coun[i]);
    		cout << M << endl;
    		for(int i = 1 ; i <= T ; i ++)
    			if(coun[i] == M)cout << ans[i] << endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    【今日CV 视觉论文速览】 19 Nov 2018
    【numpy求和】numpy.sum()求和
    【今日CV 视觉论文速览】16 Nov 2018
    【今日CV 视觉论文速览】15 Nov 2018
    poj 2454 Jersey Politics 随机化
    poj 3318 Matrix Multiplication 随机化算法
    hdu 3400 Line belt 三分法
    poj 3301 Texas Trip 三分法
    poj 2976 Dropping tests 0/1分数规划
    poj 3440 Coin Toss 概率问题
  • 原文地址:https://www.cnblogs.com/MYCui/p/13882699.html
Copyright © 2011-2022 走看看