首页 > 其他分享 >从kmp到AC自动机

从kmp到AC自动机

时间:2024-09-12 20:35:03浏览次数:10  
标签:AC 匹配 int 结点 13 next kmp 自动机 我们

知道kmp的请跳过这一段
找到最清晰的解析

kmp

我看了约114514个解析才搞懂

如何求next

首先,next[i]本应表示0~i的字符串的最长相同前缀后缀的长度。
不过为了方便匹配,实际可以存最长相同前缀后缀时前缀最后一个的地址
听起来好绕
那这么说吧:
例如串
abaabaabaab
next[0]=-1 肯定找不到
next[1]=-1 因为第一个前缀是a,它是b
next[2]=0 因为第一个前缀是a,它是a
next[3]=0 因为第一个前缀是a,它是a?
wait!不一样的点来啦!
先偷窥一下匹配的代码:

for (int i=1;i<m;i++){
    int j=next[i-1];
   	while ((s[j+1]!=s[i])&&(j>=0))j=next[j];
   	if (s[j+1]==s[i]) next[i]=j+1;
   	else next[i]=-1;
} 

也就是
a先问了它旁边那个,发现它匹配到了0
然后他也蠢蠢欲动
但下个是b
所以他的j被打到了next[2]=-1
然后循环没了
这时,他毫不惊奇地发现下一个是a
于是他就匹配上了(合理)
next[4]=1
因为它前面已经匹配到了,所以它可能是 前面匹配到的 前缀的地址+1的 那个字母
听起来还是好绕
说白了,它可以是
b先问了它旁边那个,发现它匹配到了0
然后他也蠢蠢欲动
发现下个是b
所以循环没了
这时,他毫不(是真的)惊奇地发现下一个是b
于是
abaab(内两b匹配上了)
理解了吧?
剩下的自己推,别问我
那么,现在烤馍片完成了,该做什么呢

next的作用

以下搬运自dalao题解,此处F[i]指next[i]+1
我们还是先给出一个例子:
A="abaabaabbabaaabaabbabaab"
B="abaabbabaab"
当然读者可以通过手动模拟得出只有一个地方匹配
abaabaabbabaaabaabbabaab
我们再用i表示当前A串要匹配的位置(即还未匹配),j表示当前B串匹配的位置(同样也是还未匹配),补充一下,若i>0则说明i-1是已经匹配的啦(j同理)。
首先我们还是从0开始匹配:

此时,我们发现,A的第5位和B的第5位不匹配(注意从0开始编号),此时i=5,j=5,那么我们看F[j-1]的值:
F[5-1]=2;
这说明我们接下来的匹配只要从B串第2位开始(也就是第3个字符)匹配,因为前两位已经是匹配的啦,具体请看图:

然后再接着匹配:

我们又发现,A串的第13位和B串的第10位不匹配,此时i=13,j=10,那么我们看F[j-1]的值:
F[10-1]=4
这说明B串的03位是与当前(i-4)(i-1)是匹配的,我们就不需要重新再匹配这部分了,把B串向后移,从B串的第4位开始匹配:

这时我们发现A串的第13位和B串的第4位依然不匹配

此时i=13,j=4,那么我们看F[j-1]的值:
F[4-1]=1
这说明B串的第0位是与当前i-1位匹配的,所以我们直接从B串的第1位继续匹配:

但此时B串的第1位与A串的第13位依然不匹配

此时,i=13,j=1,所以我们看一看F[j-1]的值:
F[1-1]=0
好吧,这说明已经没有相同的前后缀了,直接把B串向后移一位,直到发现B串的第0位与A串的第i位可以匹配(在这个例子中,i=13)

再重复上面的匹配过程,我们发现,匹配成功了!

这就是KMP算法的过程。
另外强调一点,当我们将B串向后移的过程其实就是i++,而当我们不动B,而是匹配的时候,就是i++,j++,这在后面的代码中会出现,这里先做一个说明。
最后来一个完整版的(dalao:话说做这些图做了好久啊!!!!):

kmp例题代码实现:

#include <bits/stdc++.h>
using namespace std;
string s1,s2;
int n,m,i,j,_next[1000010];
int main(){
	cin>>s1>>s2;
	n=s2.size();
	m=s1.size();
	_next[0]=-1;
	for (i=1;i<n;i++){
	    j=_next[i-1];
    	while ((s2[j+1]!=s2[i])&&(j>=0))j=_next[j];
    	if (s2[j+1]==s2[i]) _next[i]=j+1;
    	else _next[i]=-1;
	}
	i=0;j=0;
	while (i<m) {
		if (s1[i]==s2[j]) {
			i++;
			j++;
			if(j==n)cout<<i-n+1<<endl,j=_next[j-1]+1;
		}else{
			if (j==0)i++;
			else j=_next[j-1]+1;
		}
	}
	for (i=0;i<n;i++){
	    cout<<_next[i]+1<<" ";
	}
	return 0;
}

c,怎么这么难调
知道Trie的请跳过这一段
网上的解析

Trie

大概论述一下过程:

  1. 选定要加入到Trie树中的字符串
  2. 从根节点开始依次判断当前结点的子节点中是否包含下一个字符
  3. 如果包含,则直接访问,重复第2步
  4. 否则,则建立这个结点,继续重复第2步
  5. 若进行第2步时已经到达了最后一个字符,则直接结束
    那么,我们来举个例子
    假如,我要构建成字典树的单词是her hen hers say said
    最终构建完的字典树就长这样:
   root
  /   \
  h   s
 /     \
 e      a
/ \    / \
r n    y i
|        |
s        d

然而我们并不知道这些东西分别代表那些字符串
于是,我们对每个字符串的结尾所在的那个节点加个标记。
于是乎,我们从根节点开始,一层层依次遍历,当读入到一个加了标记的结点时,一路读到的字符连成的字符串便是原来需要储存的字符串
那么,至于怎么加入一个串呀。。。
我们也来演示一下吧、。。。
比如当前这样子:

   root
  /   \
  h   s
 /     \
 e      a
/ \    / 
r n    y 
|        
s        

其他的串都已经加入,我们现在需要加入字符串said
那么,我们从根节点开始

   ro_ot(用_表示当前节点)
  /   \
  h   s
 /     \
 e      a
/ \    / 
r n    y 
|        
s        

发现根节点的子节点里面存在s这个字符结点
把一个指针移动过去,继续找接下来的字符

   root
  /   \
  h   _s
 /     \
 e      a
/ \    / 
r n    y 
|        
s        

接着,我们惊奇的发现a也存在了,于是继续遍历

   root
  /   \
  h   s
 /     \
 e      _a
/ \    / 
r n    y 
|        
s        

这个时候,却发现当前结点不存在一个i结点,那么,我们就手动的造一个i结点出来

   root
  /   \
  h   s
 /     \
 e      a
/ \    / \
r n    y i
|        
s        

同理:

   root
  /   \
  h   s
 /     \
 e      a
/ \    / \
r n    y i
|        |
s        d

OK,上代码!

#include <bits/stdc++.h>
using namespace std;
int n,end=0;
vector<int> fa(100005),son(100005),nth(100005),sa[26](100005);//窝喜欢用vector,别喷窝啊……
void build(int where,string s,int now){
	if(now>=s.size())return;
	if(son[where]&(1<<int(c[now]-'a')))build(sa[int(c[now]-'a')][where],s,now+1);
	else{
		end++;
		fa[end]=where;
		son[where]|=(1<<int(c[now]-'a'));
		sa[int(c[now]-'a')][where]=end;
	} 
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	while(cin>>n&&n){
		fa=nth;
		son=nth;
		fail=nth;
		end=0;
		for(int i=1;i<=n;i++){
			string s;
			cin>>s;
			build(0,s,0);
		}
	}
	return 0;
}

标签:AC,匹配,int,结点,13,next,kmp,自动机,我们
From: https://www.cnblogs.com/zan-mei-tai-yang/p/18410876

相关文章

  • USACO记录
    2019Dec9.4感觉没啥难度,C的思维很好,值得学习。A简单区间dp。\(f_{l,r}\)表示只在\([l,r]\)内部覆盖得到的最大权值,转移首先将两个相邻区间\([l,k],[k+1,r]\)拼起来,以及找到覆盖点区间\([l,k-1],[k+1,r],cov(k,l,r)\),其中\(cov\)可以\(n^3\)预处理。B考虑对于每......
  • UNO 已知问题 在后台线程触发 SKXamlCanvas 的 Invalidate 且在 PaintSurface 事件抛
    本文记录一个UNO已知问题,在UNO里面可以利用SKXamlCanvas对接Skia绘制到应用里面。如果此时在后台线程里面调用SKXamlCanvas的Invalidate触发界面的重新刷新,但在具体的执行绘制PaintSurface事件里面对外抛出异常,将会导致应用炸掉背景:我准备在UNO里面将Microsoft......
  • 使用Code-Like Prompt重构ReAct
    ReAct的主要就是备用来调用函数,现在给出一个使用Code-Like的Prompt,同样支持外部函数调用稍微修改一下choose_action,应该就可以实现一次性调用多个外部工具.支持Json返回,而且返回很稳定.ReActinCode-Like#youareaprocess,followthecode.importjsonfromty......
  • Springboot + nacos + k8s 优雅停机
    需要处理的问题至少有一个服务可用K8S配置滚动部署策略服务下线后不再被调度服务关闭时主动下线nacos服务关闭时清理应用里的loadbalance实例列表缓存之前进来的请求可以返回延迟下线,最大可能保证功能结束业务服务配置nacos优雅停机优雅停机配置与最大等待......
  • Metacritic 网站中的游戏开发者和类型信息爬取
    为了从Metacritic网站上爬取游戏的开发者和类型信息,你可以使用Python的网络爬虫工具,比如requests和BeautifulSoup,或者更高级的工具如Scrapy或Selenium。本文将介绍一种基于requests和BeautifulSoup的简单爬虫示例,并假设目标是从Metacritic的单个游戏页面上提取开发......
  • Oracle数据库中的归档日志(Archive Log)详解与应用
    在Oracle数据库中,归档日志(ArchiveLog)是数据库恢复和备份策略中的一个重要组成部分。归档日志是已填充的重做日志文件组的副本,它们在数据库运行在ARCHIVELOG模式下时被保存到一个或多个脱机目标。本文将详细介绍归档日志的概念、配置、管理以及在数据库恢复中的应用。1.......
  • 单实例-oracle巡检模版 -20240912
    单实例-oracle巡检模版 -20240912——————————————————————————————————————————----2024年9月12日16:38:47----bayaim----以下内容纯属个人原创,纯属个人多年经验总结,非喜勿喷,Gun~—————————————————————......
  • oracle制作docker镜像
    1.下载官方的dockerfile构建包下载地址:https://github.com/oracle/docker-images 2.下载oracle安装包我这里下载的是19CLINUX.X64_193000_db_home.zip 最后下载的2个文件如下:[root@mastersoft]#ls-lrttotal2991400-rw-r--r--1rootroot3059705302Sep1320......
  • Apache OFBiz远程代码执行漏洞(CVE-2024-38856)
    漏洞简介ApacheOFBiz是一个开源的企业资源规划系统,提供了一整套企业管理解决方案,涵盖了许多领域,包括财务管理、供应链管理、客户关系管理、人力资源管理和电子商务等。ApacheOFBiz基于Java开发,采用灵活的架构和模块化设计,使其可以根据企业的需求进行定制和扩展,它具有强大的......
  • 基于javaC语言试题生成与考试系统的计算机毕设
    C语言试题生成与考试系统摘 要当前,网络教学方兴未艾。网上考试已在其中扮演了重要的角色,传统试卷考试方式有待提高。网络教学已从其规范性、科学性及考试工作组织、管理的统一性,影响到教学质量的好坏。基于此,本系统开发实现了基于B/S模式的c试题生成与考试系统,其中数据库采用MYSQ......