首页 > 其他分享 >广义后缀自动机略记

广义后缀自动机略记

时间:2023-08-04 22:25:20浏览次数:49  
标签:cur 后缀 text tr len int 自动机 略记

终于学 \(\text{GSAM}\) 了,这是一个非常有意思且精美的结构!

对于一颗 \(\text{Trie}\) 树 \(T\),我们可以跟处理普通字符串一样定义出它的“前缀”(根到某点的字符串),“后缀”(某点到叶子的字符串),“子串”(一条直链对应的字符串)。而它的后缀自动机被定义为接受它所有后缀的最小 \(\text{DFA}\)。

我们仿照 \(\text{SAM}\) 的定义定义出 \(\text{endpos}\) 集合,那么可以仿照单串后缀自动机的证明说明这是一个树形结构。同时在新增一个叶节点后等价类分裂的方式大体与普通的后缀自动机相同。具体证明可以参考刘研绎 2015 年的集训队论文。

也就是说,仅仅是写出代码来说,建立 \(\text{GSAM}\) 跟建立 \(\text{SAM}\) 过程大体一样!

下面是一种离线 \(\text{bfs}\) 的写法。

int tr[N][10],len[N],link[N],cnt=1;
int ch[N][10],pos[N],rk=1;
int que[N],tl;
namespace BFS{
    int extend(int c,int p){
    	int cur=++cnt;
    	len[cur]=len[p]+1;
    	while(p&&!tr[p][c]) tr[p][c]=cur,p=link[p];
    	if(p){
    		int q=tr[p][c];
    		if(len[q]==len[p]+1) link[cur]=q;
    		else{
    			int clone=++cnt;
    			memcpy(tr[clone],tr[q],lm<<2);
    			len[clone]=len[p]+1;
    			link[clone]=link[q];
    			link[cur]=link[q]=clone;
    			while(p&&tr[p][c]==q) tr[p][c]=clone,p=link[p];
    		}
    	}
    	else link[cur]=1;
    	return cur;
    }
    void solve(){
        que[tl=1]=1;
        for(int ps=1;ps<=tl;++ps){
            int u=que[ps];
            for(int c=0;c<lm;++c)
                if(ch[u][c]){
                    pos[ch[u][c]]=extend(c,pos[u]);
                    que[++tl]=ch[u][c];
                }
        }
    }
}

我们注意到与原版 \(\text{SAM}\) 的唯一区别就是在 \(\text{las}\) 节点继承方面,\(\text{GSAM}\) 需要直接继承 \(\text{Trie}\) 上的父亲。

可为什么一定要用 \(\text{bfs}\) 呢?有两个很重要的理由,暂且按下不表。

事实上我们还有一种 \(\text{dfs}\) 的写法,它在某些方面更有优势,比如它支持在线插入一个模式串。

int tr[N][10],len[N],link[N],cnt=1;
int ch[N][10],pos[N],rk=1;
namespace DFS{
    int extend(int c,int p){
        if(tr[p][c]){
            int q=tr[p][c];
            if(len[q]==len[p]+1) return q;
            int clone=++cnt;
            memcpy(tr[clone],tr[q],lm<<2);
            len[clone]=len[p]+1;
            link[clone]=link[q];
            link[q]=clone;
            while(p&&tr[p][c]==q) tr[p][c]=clone,p=link[p];
            return clone;
        }
        int cur=++cnt;
        len[cur]=len[p]+1;
        while(p&&!tr[p][c]) tr[p][c]=cur,p=link[p];
        if(p){
            int q=tr[p][c];
            if(len[q]==len[p]+1) link[cur]=q;
            else{
                int clone=++cnt;
                memcpy(tr[clone],tr[q],lm<<2);
                len[clone]=len[p]+1;
                link[clone]=link[q];
                link[cur]=link[q]=clone;
                while(p&&tr[p][c]==q) tr[p][c]=clone,p=link[p];
            }
        }
        else link[cur]=1;
        return cur;
    }
    void solve(int u=1){
        for(int c=0;c<lm;++c)
            if(ch[u][c]){
                pos[ch[u][c]]=extend(c,pos[u]);
                solve(ch[u][c]);
            }
    }
}

注意到 \(\text{dfs}\) 的区别除了是按深度优先序建立的,而且代码中还多了一块特判。

这是为什么呢?因为在插入新的前缀节点 \(S+x\) 时,在原先的 \(\text{Trie}\) 树上可能已经出现过形如 \(\dots+S+x\) 这样的串,此时代码中的 \(q\) 会指向原先的 \(S+x\) 所在的等价类,将该等价类分裂后会得到一个 \(\text{maxlen}=|S|+1\) 的新等价类,此时如果我们新建了一个 \(\text{cur}\) 指向这个新等价类,那么 \(\text{cur}\) 所在的等价类表示的就是一个空串,与 \(\text{DFA}\) 的最小性不符!

解决方法很简单:如果已经有了 \(S+x\) 这个串,不新建节点就行了!

为什么 \(\text{bfs}\) 没有这个问题呢?因为 \(\dots+S+x\) 不可能在 \(S\) 之前被插入!这是 \(\text{bfs}\) 的第一个优势。

第二个优势是可以证明,\(\text{bfs}\) 的复杂度是关于 \(|T|\) 也就是 \(\text{Trie}\) 的节点数线性(带字符集常数),

标签:cur,后缀,text,tr,len,int,自动机,略记
From: https://www.cnblogs.com/yyyyxh/p/17607176.html

相关文章

  • WebService如何去掉后缀访问
    创建全局应用程序类Global.asax,在方法Application_BeginRequest并添加如下代码:利用替换的方式实现效果stringpath=Request.Url.ToString();path=Request.Url.LocalPath.ToString();if(path=="/IFS"){Contex......
  • 后缀数组(SA)做题记录
    SA真的是个好东西,好呀好东西。基础定义:$sa$数组:后缀排序后排名为$i$的后缀的起始位置下标。$rk$数组:起始下标为$i$的后缀的排名。$height$数组:后缀排序后排名为$i$和$i-1$的最长公共前缀长度(Lcp)模板:(小bug:在SA代码charch[N];structSuffix_Array{llm=......
  • 后缀自动机的应用
    后缀自动机的原理就不在赘述了,这里主要介绍它的应用。板子:structnode{ intc[26],len,fa;}a[maxn];voidbuild(intx){ intp=las;intnp=las=++tot; a[np].len=a[p].len+1; for(;p&&!a[p].c[x];p=a[p].fa) a[p].c[x]=np; if(!p) a[np].fa=1; else{ intq=a[p].......
  • 后缀排序
    后缀排序本文做复习用,不宜初学用。定义\(sa\)表示排名为\(i\)的位置。\(rk\)表示位置为\(i\)的排名。\(y\)表示按照第二关键字排序排名为\(i\)的位置。\(height\)表示排名为\(i\)和\(i-1\)的后缀的最大前缀\(h\)表示位置为\(i\)和它排名前一位的后缀......
  • 算法学习笔记(27): 后缀排序
    后缀排序本文做复习用,不宜初学用。开篇膜拜Pecco:算法学习笔记(84):后缀数组-知乎(zhihu.com)有些时候,其实\(O(n\log^2n)\)的排序也挺好。又短又简单。其中\(rk[i]\)表示从第\(i\)个字符开始的后缀的排名,\(sa[i]\)表示排名为\(i\)的后缀开始的位置。#includ......
  • npm常用后缀
    –--save、-S参数意思是把模块的版本信息保存到dependencies(生产环境依赖)中,即你的package.json文件的dependencies字段中;–--save-dev、-D参数意思是把模块版本信息保存到devDependencies(开发环境依赖)中,即你的package.json文件的devDependencies字段中;–--save-optional......
  • android mount文件后缀
    实现AndroidMount文件后缀的步骤作为一名经验丰富的开发者,我将教会你如何实现AndroidMount文件后缀的功能。下面是实现这一功能的步骤和具体代码解释。步骤一:配置AndroidManifest.xml文件在AndroidManifest.xml文件中添加以下权限和文件类型声明:<uses-permissionandroid:nam......
  • 为什么文件后缀改了.java显示还是文本文件
    为什么文件后缀改了.java显示还是文本文件在计算机中,文件后缀用于标识文件的类型。根据文件后缀,操作系统会使用相应的程序来打开、编辑或执行文件。例如,文件后缀为".txt"的文件会被认为是文本文件,并使用文本编辑器打开。而文件后缀为".java"的文件则会被认为是Java源代码文件,并使......
  • 「学习笔记」AC 自动机
    AC自动机是 以Trie的结构为基础,结合 KMP的思想 建立的自动机,用于解决多模式匹配等任务。Trie的构建这里需要仔细解释一下Trie的结点的含义,Trie中的结点表示的是某个模式串的前缀。我们在后文也将其称作状态。一个结点表示一个状态,Trie的边就是状态的转移。形式化地......
  • 「学习笔记」自动机家族
    OI中所说的「自动机」一般都指「确定有限状态自动机」。一个确定有限状态自动机(DFA)由以下五部分构成:字符集(\(\Sigma\)),该自动机只能输入这些字符。状态集合(\(Q\))。如果把一个DFA看成一张有向图,那么DFA中的状态就相当于图上的顶点。起始状态(\(start\)),\(start\inQ\),是一......