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;
    }
    
  • 相关阅读:
    P1659 [国家集训队]拉拉队排练
    manacher小结
    P4555 [国家集训队]最长双回文串
    P3649 [APIO2014]回文串
    P3899 [湖南集训]谈笑风生
    插头dp练习
    luoguP3066 [USACO12DEC]逃跑的BarnRunning
    luoguP3769 [CH弱省胡策R2]TATT
    android 广播,manifest.xml注册,代码编写
    *.db-journal 是什么(android sqlite )数据库删除缓存
  • 原文地址:https://www.cnblogs.com/MYCui/p/13882699.html
Copyright © 2011-2022 走看看