广义后缀自动机小结

人类的本质是自动复读机(划掉)

前置芝士:后缀自动机(SAM)

正文

后缀自动机是一种优秀的处理单串的数据结构,同时部分多个串相关的问题也可以使用其进行处理,但是对于这个题而言单纯的后缀自动机便显得有些乏力。

这个时候就要请出广义后缀自动机了,广义后缀自动机可以看成是在\(\mathrm{Trie}\)上建出\(\mathrm{SAM}\), 其构造方法基于单串\(\mathrm{SAM}\), 同时又需要考虑多个串。主流的写法大致有一下两种:

  • 离线做法:首先将\(\mathrm{Trie}\)建出,然后在\(\mathrm{Trie}\)上使用bfs进行构建,在insert每个点的时候其\(p\)值就是它在\(\mathrm{Trie}\)的父亲在自动机的位置。注意不能使用dfs因为会被卡成\(O(n^2)\).(注:笔者的相关代码可以在这里找到)

  • 在线做法:每次插入一个串的时候将\(\mathrm{last}\)直接设为\(1\)然后跑正常的构建。

离线做法的insert和单串\(\mathrm{SAM}\)的没有太大差别,但是在线做法由于会出现一个小bug所以需要进行适当的修改,代码如下:

void insert(int x,int id)
{
	if ((ch[lst][x]) && (len[ch[lst][x]]==len[lst]+1)) 
	{
		lst=ch[lst][x];return;
	}//特判1
	int np=(++tot),p=lst,flag=0;len[np]=len[p]+1;
	while ((p) && (!ch[p][x])) {ch[p][x]=np;p=fa[p];}
	if (!p) fa[np]=1;
	else
	{
		int q=ch[p][x];
		if (len[q]==len[p]+1) fa[np]=q;
		else
		{
			if (len[np]==len[p]+1) flag=1;//特判2
			int nq=(++tot);len[nq]=len[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[nq]));
			fa[nq]=fa[q];fa[q]=fa[np]=nq;
			while ((p) && (ch[p][x]==q)) {ch[p][x]=nq;p=fa[p];}
			if (flag) np=nq;
		}
	}
	lst=np;
}

主要的区别就是加上了2个特判,简单的概括一下,由于我们将多个串的\(\mathrm{SAM}\)合并成了1个,会导致一些对于当前串而言,本不应该存在的节点由于已处理的串的需要而被构建了出来。
第一个特判表示当前已经存在一个我要新建的节点。第二个特判表示在正常的\(\mathrm{SAM}\)处理的分裂节点中,分裂出来的新节点就是我们原来需要的节点。更详细的论证过程可以参见这篇blog(由于笔者语文水平有限故选择直接放上链接)

广义后缀自动机的其它定义相较单串的后缀自动机并无变化,需要注意的是对于某些信息,对每个串都需要单独处理而不是全部混在一起。

例题

模板题:https://www.cnblogs.com/encodetalker/p/12528369.html

posted @ 2020-03-19 23:45  EncodeTalker  阅读(157)  评论(0编辑  收藏  举报