Loading

AC自动机

感觉这个知识点东西有点多,我现在有点混乱。(找到了\(Tarjan\)一算有八板,八板各不同的感觉)
先贴一个学习\(blog\)(我就知道我会忘)

< 先咕一下四道题的题解,有时间再写。

我习惯打的\(ins\)\(build\)

inline void ins( char s [] , int ix ){
	int len = strlen( s + 1 ) , x = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'a' ;
		if( !tr [x][ch] ) tr [x][ch] = ++ cnt ;
		x = tr [x][ch] ;
	} st [x] |= ( 1 << ( ix - 1 ) ) ;//最后一行因题而异,看看具体需要什么操作
}

void build(){
	queue< int > q ;
	for( R i = 0 ; i < 26 ; i ++ ) if( tr [0][i] ) q.push( tr [0][i] ) ;
	
	while( !q.empty() ){
		int x = q.front() ; q.pop() ;
		for( R i = 0 ; i < 26 ; i ++ ){
			if( tr [x][i] ) fail [tr [x][i]] = tr [fail [x]][i] , st [tr [x][i]] |= st [tr [fail [x]][i]] , q.push( tr [x][i] ) ;
			//这个从fail指针传递信息同样因题而异
			else tr [x][i] = tr [fail [x]][i] ;
		}
	} 
}

这个东西一般有两种操作

操作一:就在\(Tire\)图上跑,每次查询对于\(text\)的每个字符暴力跳\(fail\)

例题:\(luogu\)模板一
\(fail\)指针定义还是挺重要的。
对于一个\(tire\)中的节点,他的实际意义是一个字符串的前缀。
他的\(fail\)指向他这个前缀所能匹配到的最长后缀的节点。
既然是后缀,那么深度一定比他小。
然后对于每个点跳\(fail\)就不难理解了。
假设已经把\(mode\)串建完了\(AC\)自动机,现在正在跑\(text\)串。
我们其实想知道这个\(text\)在自动机上可以经过哪些节点。
跑到每个节点,我们都要跳\(fail\)直到根节点,这是因为如果一个前缀\(x\)被跑过了,那么这个前缀\(x\)\(fail\)也应该被跑过。
所以不断跳\(fail\)并打上标记,最终,\(tire\)图上有标记的点就是\(text\)能跑到的所有点。
当然,我们在\(ins\)的时候对所有末尾点打上标记,那么对于经过的点加上他是否有标记就行了。

inline int ask( char s [] ){
	int len = strlen( s + 1 ) , x = 0 , ans = 0 ;
	for( R i = 1 ; i <= len ; i ++ ){
		int ch = s [i] - 'a' + 1 ; x = tr [x][ch] ;
		for( R j = x ; j != 0 && ends [j] != -1 ; j = fail [j] )
			ans += ends [j] , end [j] = -1 ; 
	} return ans ;
}

假设所有\(mode\)串的\(\sum len\)\(n\).
这样查一个长度为\(m\)\(text\)串,时间复杂度是\(O(n+m)\)
\(luogu\)模板二还是暴力跳\(fail\),就是改变了一下\(end\)存的信息,没什么好说的。

但其实还有一点可说的,模板二直接跳\(fail\)最会复杂度是\(O(nm)\),但是模板一确实是\(O(n+m)\)
因为模板一里面每个\(Tire\)中的节点最多经过一次,但是模板二可能一个点经过很多次。
虽然是\(O(nm)\),但是严重跑不满,导致可以水过去。

病毒 其中的思路不错,需要记录一下,一个串可以无限长而不出现给定的串,一定是他基于什么循环了,所以问题就变成了判环。

这里记录一下\(dfs\)判环

code
void dfs( int x ){
	fg [x] = 1 ;
	for( R i = head [x] ; ~i ; i = net [i] ){
		int y = to [i] ;
		if( .... )  continue ; //终止条件
		if( fg [y] ) .... // 有环
		if( ! vis [y] ) vis [y] = 1 , dfs( y ) ; //接着扫
	} fg [x] = 0 ; // 回溯
}


一定要注意\(end\)\(c++11\)保留字,看编译参数输编译指令。

这个东西属实很慢。所以,当信息不重复时,我们可以考虑让\(x\)继承\(fail_x\)的信息,因为\(fail_x\)能干的,\(x\)一定能干。
具体应用:最短母串 文本生成器,密码
这里解释一下什么叫信息不重复。
对于模板一,信息就是重复的,因为每个点都可能同时是很多字符串的\(end\),导致他有很多信息,存不下。
对于文本生成器,我们只需要知道每个点是不是结尾。
对于状压的那两道,信息是可合并的,因为我们可以把它或起来。
要先判断信息是不是可以合并,如果不可以基本就不要考虑(傻孩子还想过搞个权值线段树或者平衡树啥的维护呢。)

一般需要跳\(fail\)的就有暴力跳和传信息两种,主要看信息重不重复。

操作二:先搞\(Tire\)图,标记完信息之后用\(fail\)树解决。

学习\(blog\)
有些操作,我们既不能合并信息,有不能暴力跳\(fail\)(时间不允许)
这个时候,我们就要搞出来一个东西—\(fail\)树。
直接字面理解就行了,就是\(fail\)指针弄出来的树,每条边都是一个反向的\(fail\)指针。
这样,我们可以先在\(Tire\)上跑一遍,把走过的地方都标记上,最后再\(dfs\)一遍,继承子树的标记。
继承子树的标记相当与\(fail_x\)继承\(x\)的标记。
其中模板题有
\(luogu\)模板3 纯模板
单词 把每个串都当成模板串,记录把所有串都跑一遍每个点经过的次数,最后直接一查就行了。
玄武密码 很水的模板题,把母串在自动机上跑完,记录到跑过的点,然后直接从小到大枚举每个串匹配几个字符就行。


背单词 是一个很套路的题,单独写一个博客,点链接就行。


操作三:跑\(AC\)自动机,得到\(tr\)之后在上面\(dp\)

很套路,没什么新奇的,一般有一维是"当前在\(AC\)自动机的某个节点上",在根据条件设计下一维即可。
最短母串 文本生成器,密码 背单词
禁忌 由于长度很长,不难想到要用矩阵进行优化。

posted @ 2021-06-30 21:29  Soresen  阅读(102)  评论(3)    收藏  举报