AC自动机学习笔记

这个东西挺神奇的,巧妙的把KMP搬到了trie上
说一说最重要的失配函数
这里和KMP不一样,KMP的失配函数是链接到自己前面的某个位置,而AC自动机的则是trie的其他子树上,意义和KMP倒是一样,那么如何实现呢,我们通过一次对trie的bfs实现

void getfail()
{
	queue<int>q;f[0]=0;
	for(int i=0;i<26;i++)
		if(ch[0][i])f[ch[0][i]]=0,q.push(ch[0][i]);// 很显然,第一层节点的指针一定指向总根
	while(!q.empty())
	{
		int now=q.front();q.pop();
		for(int i=0;i<26;i++)
		{
			int u=ch[now][i];
			if(!u)ch[now][i]=ch[f[now]][i];// 如果不存在就有点意思,为了防止f函数跳的次数过多,		
			// 我们直接将trie树该节点的i儿子连向失配指针指向的地方,可以做到加速的作用
			else q.push(u),f[u]=ch[f[now]][i];// 因为是bfs,所以当处理到这一层的时候,他的父
			// 节点的f函数一定预处理过了,那么,可以直接连接到它父节点的失配指针的下一层
			// 这样可以达到一层层深入的目的
		}
	}
}

可能会有几个问题,我们画个图
在这里插入图片描述
我们先看最下面的h,跑到它的时候,它的父节点t的失配指针指向0点,此时h的指针就为它父节点 t的h号儿子
我们再看右下方的那个e,在跑到它的时候,它的父节点h已经得出了失配函数为上方的h
这时它的失配函数就是它父节点失配指针的e号儿子

重复上面的过程即可

匹配的话,就比较简单,因为有了那个在trie上加边的操作,甚至连while都不用写…

void find(string a)
{
	int len=a.length();
	int j=0;
	for(int i=0;i<len;i++)
	{
		j=ch[j][a[i]-'a'];
		for(int t=j;t;t=f[t])ans[E[t]].num+=End[t];
	}
}

忽略奇怪的东西,上两题代码
洛谷P3796 【模板】AC自动机(加强版)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 5e5+7;
int ch[maxn][26],cnt;
int n,End[maxn],E[maxn];

void putin(string a,int pos)
{
	int root=0;
	for(int i=0;i<a.length();i++)
	{
		if(!ch[root][a[i]-'a'])ch[root][a[i]-'a']=++cnt;
		root=ch[root][a[i]-'a'];
	}
	End[root]++;
	E[root]=pos;
}

int last[maxn],f[maxn];

void getfail()
{
	queue<int>q;f[0]=0;
	for(int i=0;i<26;i++)
		if(ch[0][i])f[ch[0][i]]=0,q.push(ch[0][i]);
	while(!q.empty())
	{
		int now=q.front();q.pop();
		for(int i=0;i<26;i++)
		{
			int u=ch[now][i];
			if(!u)ch[now][i]=ch[f[now]][i];
			else q.push(u),f[u]=ch[f[now]][i];
		}
	}
}

struct out
{
	int pos,num;
	friend bool operator <(out a,out b)
	{
		if(a.num!=b.num)return a.num>b.num;
		else return a.pos<b.pos;
	};
}ans[200];


void find(string a)
{
	int len=a.length();
	int j=0;
	for(int i=0;i<len;i++)
	{
		j=ch[j][a[i]-'a'];
		for(int t=j;t;t=f[t])ans[E[t]].num+=End[t];
	}
}

string s[200];
int main()
{
	
	while(1)
	{
		scanf("%d",&n);
		if(n==0)break;
		for(int i=1;i<=n;i++)
		{
			string into;
			cin>>into;
			s[i]=into;
			putin(into,i);
			ans[i].pos=i;
		}
		getfail();
		string T;
		cin>>T;
		find(T);
		sort(ans+1,ans+1+n);
		cout<<ans[1].num<<endl<<s[ans[1].pos]<<endl;
		for(int i=2;i<=n;i++)
		{
			if(ans[i].num==ans[i-1].num)cout<<s[ans[i].pos]<<endl;
			else break;
		}
		memset(ans,0,sizeof(ans));
		memset(f,0,sizeof(f));
		memset(End,0,sizeof(End));
		memset(E,0,sizeof(E));
		memset(ch,0,sizeof(ch));
	}
	return 0;
}

洛谷P3808 【模板】AC自动机(简单版)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 5e6+7;
int ch[maxn][26],cnt;
int n,end[maxn];

void putin(string a)
{
	int root=0;
	for(int i=0;i<a.length();i++)
	{
		if(!ch[root][a[i]-'a'])ch[root][a[i]-'a']=++cnt;
		root=ch[root][a[i]-'a'];
	}
	end[root]++;
}

int last[maxn],f[maxn];

void getfail()
{
	queue<int>q;f[0]=0;
	for(int i=0;i<26;i++)
		if(ch[0][i])f[ch[0][i]]=0,q.push(ch[0][i]);
	while(!q.empty())
	{
		int now=q.front();q.pop();
		for(int i=0;i<26;i++)
		{
			int u=ch[now][i];
			if(!u)ch[now][i]=ch[f[now]][i];
			else q.push(u),f[u]=ch[f[now]][i];
		}
	}
}

void find(string a)
{
	int len=a.length();
	int j=0,ans=0;
	for(int i=0;i<len;i++)
	{
		j=ch[j][a[i]-'a'];
		for(int t=j;t&&~end[t];t=f[t])ans+=end[t],end[t]=-1;
	}
	cout<<ans;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		string into;
		cin>>into;
		putin(into);
	}
	getfail();
	string T;
	cin>>T;
	find(T);
	return 0;
}

二次加强版还没写,有点麻烦…鬼知道等啥时闲下来吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值