【Ybt OJ】[字符串算法 第5章] AC自动机 [前半章]

45 篇文章 0 订阅
2 篇文章 0 订阅

「 「 字符串算法 」 」 5 5 5 A C AC AC自动机 ( ( ( 3 3 3 ) ) )
目录:

A.单词查询
B.单词频率
C.前缀匹配

A . A. A. 例题 1 1 1 单词查询

在这里插入图片描述
洛谷 l i n k link link

分析:

A C AC AC自动机模板 一般用来求 有多少个不同的模式串文本串里出现 这类问题
可以简单理解成 将 K M P KMP KMP放在 t r i e trie trie树上

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#include<bitset>
//#pragma GCC optimize(2)
#define reg register 
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int n,tot,q[N],lens,lena;
char s[N],a[N];
struct Trie{
	int qwq,end,son[30];
}trie[N];
void insert(char ovo[])
{
	int now=0;
	for(int i=0;i<lens;i++)
	{
		int k=ovo[i]-'a';
		if(!trie[now].son[k]) trie[now].son[k]=++tot;
		now=trie[now].son[k];
	}
	trie[now].end++;
}
void work()
{
	int l=0,r=0;
	for(int i=0;i<26;i++)
		if(trie[0].son[i]!=0)
		{
			trie[trie[0].son[i]].qwq=0;
			q[++r]=trie[0].son[i];
		}
	while(l<r)
	{
		int front=q[++l];
		for(int i=0;i<26;i++)
		{
			int x=trie[front].son[i];
			if(x)
			{
				trie[x].qwq=trie[trie[front].qwq].son[i];
				q[++r]=x;
			}
			else trie[front].son[i]=trie[trie[front].qwq].son[i];
		}
	}
}
int AC(char ovo[])
{
	int now=0,ret=0;
	for(int i=0;i<lena;i++)
	{
		int k=ovo[i]-'a';
		now=trie[now].son[k];
		int p=now;
		while(p&&trie[p].end!=-1)
		{
			ret+=trie[p].end;
			trie[p].end=-1;
			p=trie[p].qwq;
		}
	}
	return ret;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s);
			lens=strlen(s);
			insert(s);
		}
		trie[0].qwq=0;
		work();
		scanf("%s",a);
		lena=strlen(a);
		printf("%d\n",AC(a));
		memset(trie,0,sizeof(trie));
	}
	return 0;
}

B . B. B. 例题 2 2 2 单词频率

在这里插入图片描述
洛谷 l i n k link link

分析:

此题可 f i n d ( ) find() find()过 复杂度大约 O ( N 2 ∗ l e n S ) O(N^2*lenS) O(N2lenS)
开了 O 2 O2 O2复杂度还是可观的

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=215;
string a[N];
int n,cnt[N],p;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			int k=0;
			p=a[j].find(a[i],k);
			while(k<a[j].length()&&p!=-1)
			{
				k=p+1;  //更新
				p=a[j].find(a[i],k);
				cnt[i]++;
			}
		}
	for(int i=1;i<=n;i++)
		printf("%d\n",cnt[i]);
	return 0;
}
A C AC AC自动机:

把所有给出的单词 连成文本串 再求每个出现次数
那存 t r i e trie trie时 就一直给当前单词加出现次数 然后正常 A C AC AC自动机

f a i l fail fail到这个点 就说明前面一段一定是匹配
然后就在 f a i l fail fail上求前缀和 就是次数了

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#include<bitset>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int n,tot,sum[N],fail[N],num[N];
char s[N];
struct AC{
	int end,son[27];
}trie[N];
void ins(int x)
{
	scanf("%s",s+1);
	int now=0,len=strlen(s+1);
	for(int i=1;i<=len;i++)
	{
		int qwq=s[i]-'a';
		if(!trie[now].son[qwq]) trie[now].son[qwq]=++tot;
		now=trie[now].son[qwq];
		sum[now]++;
	}
	trie[x].end=now;  //记录位置
}
void build()
{
	int head=0,tail=0;
	for(int i=0;i<26;i++)
		if(trie[0].son[i])
			num[++tail]=trie[0].son[i];
	while(head<tail)
	{
		int a=num[++head],b;
		for(int i=0;i<26;i++)
		{
			b=trie[a].son[i];
			if(b)
			{
				num[++tail]=b;
				fail[b]=trie[fail[a]].son[i];
			}else
				trie[a].son[i]=trie[fail[a]].son[i];
		}	
	}
}
void query()
{
	for(int i=tot;i>=0;i--) sum[fail[num[i]]]+=sum[num[i]];  //求fail的前缀和
	for(int i=1;i<=n;i++) 
		printf("%d\n",sum[trie[i].end]);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		ins(i);
	build();
	query();
		
	return 0;
}

C . C. C. 例题 3 3 3 前缀匹配

在这里插入图片描述
洛谷 l i n k link link

分析:

注意到 每个字符串长度都很 肯定不能普通跑 A C AC AC自动机

我们就可以 枚举每个模式串的前缀 然后看有没有在文本串里出现
预处理就行了

如果有 那就找最长长度的前缀 就表示原本的模式串
然后就可以输出这个最长前缀 a n s ans ans

因为它只有 4 4 4种字符 那就可以给每种字符一个 v a l u e value value 就可以节省空间
正常 − - ′ A ′ 'A' A也可以 但效率会慢一些. c o d e code code − - ′ A ′ 'A' A

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#include<bitset>
#define Ctnue continue
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e7+5,M=1e5+5;
int n,m,ans[M],tot;
char s[N],ch[M][105];
queue<int> q;
struct Trie{
	int son[27],fail;
	bool end;
}trie[N];
void add(int x)
{
	int len=strlen(ch[x]),now=0;
	for(int i=0;i<len;i++)
	{
		int qwq=ch[x][i]-'A';
		if(!trie[now].son[qwq]) trie[now].son[qwq]=++tot;
		now=trie[now].son[qwq];
	}
}
void AC()
{
	for(int i=0;i<=26;i++)
		if(trie[0].son[i])
		{
			q.push(trie[0].son[i]);
			trie[trie[0].son[i]].fail=0;
		}
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		for(int i=0;i<=26;i++)
		{
			if(trie[now].son[i])
			{
				q.push(trie[now].son[i]);
				trie[trie[now].son[i]].fail=trie[trie[now].fail].son[i];
			}
			else trie[now].son[i]=trie[trie[now].fail].son[i];
		}
	}
}
void Pre()
{
	int now=0;
	for(int i=1;i<=n;i++)  //枚举前缀
	{
		int qwq=s[i]-'A';
		int x=trie[now].son[qwq];
		while(x)  //看有没有存在
		{
			trie[x].end=1;
			x=trie[x].fail;
		}
		now=trie[now].son[qwq];
	}
}
int query(int x)
{
	int ans=0,len=strlen(ch[x]);
	int now=0;
	for(int i=0;i<len;i++)
	{
		int qwq=ch[x][i]-'A';
		now=trie[now].son[qwq];
		if(trie[now].end) ans=i+1;  //枚举前缀的 最长长度
	}
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		cin>>s[i];
	for(int i=1;i<=m;i++)
	{
		scanf("%s",ch[i]);
		add(i);
	}
	AC();Pre();
	for(int i=1;i<=m;i++)
		printf("%d\n",query(i));
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值