首页 > 其他分享 >SAM & 广义 SAM & SA 学习笔记

SAM & 广义 SAM & SA 学习笔记

时间:2024-01-30 23:22:25浏览次数:15  
标签:SAM nd len son fa 笔记 nq SA

SAM

定理

SAM 由 parent 树与一张 DAG 构成,他们共用点集。

\(endpos(s)\) 表示 \(s\) 出现的所有位置上最后一个字符所处位置的集合。

SAM 中 DAG 上每条路径对应原串上的一个子串,一个子串也与其对应。

在 SAM 的 DAG 上到达一个点的所有子串的 endpos 相同。

一个节点上储存的最长的串为 \(len_i\)。

在 parent 树上点 \(u\) 的父亲节点是与 \(u\) endpos 不相同的 \(len_{u}\) 的最长真后缀。

不难发现点 \(u\) 的 endpos 包含所有满足 \(v \in son_{u}\) 的 \(v\) 的 endpos,不难发现一个点 \(u\) 存储的串就是所有长度小于等于自己并大于其父节点的所有后缀。

构建

增量法

已知串 \(S\) 的 SAM,如何构造 \(S+c\) 的 SAM?

找到一个节点 \(u\) 使得 \(len_{u} = |S|\)。

考虑在 parent 树上 \(u\) 的所有祖先包含 \(S\) 的所有真后缀。

在这条路径上显然应当是满足所有深度大于 \(t\) 的点没有 \(c\) 这条出边。

新建一个点 \(p\) 使得其 \(len_{p} = len_{u} + 1\) 等于说是包括了 \(c\)。

让所有没有 \(c\) 这条出边的点向 \(p\) 连一条 \(c\) 的边。

到此为止 DAG 的维护就完成了。

接下来考虑维护 parent 树与 endpos。

大力分讨。

  1. 假设所有点都向 \(p\) 连边,就说明 \(c\) 根本就没出现过,那么其所有后缀的 endpos 都是 \(len_{p}\) 那么将 \(p\) 在 parent 树上的父亲改为根即可。

  2. 假设存在点向 \(p\) 连边,找到第一个存在向 \(c\) 的边的点 \(x\),假设其向 \(c\) 的边指向 \(q\),假若 \(len_{x} + 1 = len_{q}\),直接令 \(fa_{p} = q\)。因为 \(p\) 最长只能跳这来。

  3. 假若 \(len_{x} + 1 \not = len_{q}\) 那就有点麻烦,那么此时需要分裂 \(q\),假设你分裂出了新点 \(nq\),直接令 \(SAM_{nq} = SAM_{q}\),然后手动将 \(SAM_{nq,len} = SAM_{x,len} + 1\),然后让 \(SAM_{q,fa} = nq\),然后让 \(SAM_{p,fa} = nq\)。但是此时 DAG 的性质又似了,我们需要把所有 \(x\) 的祖先当中原本指向 \(q\) 的点全部指向 \(nq\)。

至此你发现 parent 树和 endpos 全部维护好了。

复杂度是神奇的线性乘字符集。

不难发现一个点的最长串一定都这个点上不会被分类,那么每次插入字符新建的点一定代表一个前缀,跳 parent 树即可跳出子串。

广义 SAM

其实差不多。

有点小区别,当你求所有串的公共子串时,需要把每个子串的公共子串都遍历一遍,自己遍历过的点自己不再遍历但是其他点仍需要遍历,复杂度是 \(O(len \sqrt len)\) 的。

考虑一个串的遍历复杂度是 \(O(len^2,\sum |S|)\) 那么均摊下来一个字符的复杂度是 \(O(\min(len,\frac{\sum |S|}{len}) \leq O(\sqrt{\sum |S|})\),所以总复杂度是 \(O(\sum |S| \sqrt{\sum |S|})\)。

SA

后缀数组 \(rk_{i}\) 表示字典序第 \(i\) 的后缀为 \([rk_{i},n]\)。

考虑怎么构造。

有一个无脑的 \(O(n \log^2 n)\) 做法,不提了。

考虑倍增。

考虑把每个 \([i,i+2^k]\) 拿出来排序,因为上一次已经排好了,所以用两个关键字做基数排序即可。

height 数组

令 \(lcp(i,j)\) 表示 \([rk_i,n]\) 与 \([rk_j,n]\) 的最长公共前缀,有 \(lca(i,j) = \min(lca(i,j),lca(j,k))\),假若我们处理出 \(height_{i} = lcp(i,i-1)\) 就能把 \(lca(i,j)\) 变成 RMQ 问题。

考虑求出 \(height\) 数组。这里给一个新数组 \(h\) 数组。

考虑 \(height_{i} = h_{rk{i}}\)。

有 \(h_{i+1} \geq h_{i} - 1\) 然后考虑去暴力扩展。

大不了全部扩展完,是 \(O(n)\) 的。

板子

广义 SAM

struct SAM{
	int len,fa;
	int son[26];
}nd[maxn]; 
int tot,lst;
void ins(char c){
    if(nd[lst].son[c-'a']!=0){
    	int q=nd[lst].son[c-'a'],v=lst;
        if(nd[q].len==nd[v].len+1){
        	lst=q;
        	return ;	
		}
        int nq=++tot;
        lst=nq;
        nd[nq]=nd[q];
        nd[nq].len=nd[v].len+1;
        nd[q].fa=nq;
        while(v!=0&&nd[v].son[c-'a']==q) nd[v].son[c-'a']=nq,v=nd[v].fa;
        return ;
    }
    int u=++tot,v=lst;
	nd[u].len=nd[lst].len+1;
  	lst=u;
	while(v!=0&&nd[v].son[c-'a']==0) nd[v].son[c-'a']=u,v=nd[v].fa;
	if(v==0){
        nd[u].fa=1;
        return ;
    }else{
        int q=nd[v].son[c-'a'];
        if(nd[v].len+1==nd[q].len){
            nd[u].fa=q;
            return ;
        }else{
            int nq=++tot;
            nd[nq]=nd[q];
            nd[nq].len=nd[v].len+1;
            nd[u].fa=nq;
            nd[q].fa=nq;
            while(v!=0&&nd[v].son[c-'a']==q) nd[v].son[c-'a']=nq,v=nd[v].fa;
            return ;
        }
    }
}
int rt;
void insert(string s){
    lst=rt;
    for(char x:s) ins(x);
}

标签:SAM,nd,len,son,fa,笔记,nq,SA
From: https://www.cnblogs.com/chifan-duck/p/17998206

相关文章

  • 【学习笔记】排序
    选择排序选择排序是一种简单的排序算法。它的原理是每次找到数组中的最小值放到正确的位置。选择排序的最好、最坏、平均时间复杂度都是\(O(n^2)\),空间复杂度为\(O(1)\)。由于存在交换这一操作,选择排序是一个不稳定的排序算法。voidselectionSort(inta[],intn){ for(int......
  • 大三寒假学习进度笔记21
    今天看到了一道蓝桥杯的题目,其中使用到了dfs算法,在之前的数据结构中学习过这种算法,但是并没有在代码中使用过,因此根据给出的思路在写了一遍这个题目。#include<bits/stdc++.h>usingnamespacestd;inta[100],ans=0;boolvis[20240000];boolcheck(intdate){if(vis[d......
  • F - Negative Traveling Salesman
    F-NegativeTravelingSalesmanProblemStatementThereisaweightedsimpledirectedgraphwith$N$verticesand$M$edges.Theverticesarenumbered$1$to$N$,andthe$i$-thedgehasaweightof$W_i$andextendsfromvertex$U_i$tovertex$V_i$.The......
  • CSAPP学习笔记——chapter5 优化程序性能
    编写高效程序需要做到以下几点:第一,我们必须选择一组适当的算法和数据结构第二,我们必须编写出编译器能够有效优化以转换成高效可执行代码的源代码。对于这第二点,理解优化编译器的能力和局限性是很重要的。编写程序方式中看上去只是一点小小的变动,都会引起编译器优化方式很大的变化......
  • 【侯捷C++面向对象笔记】补充5-new & delete重载
    平时所使用的new和delete操作,称之为表达式,一般由好几个步骤组成。如上图所示,new表达式会被编译器转化为三个步骤。new表达式不能重载,但其中operatornew是可以重载的。➡️全局::operatornew的重载why不能放在namespace内?因为全局operatornew是放在defaultglobalnamespac......
  • openGauss学习笔记-211 openGauss 数据库运维-高危操作一览表
    openGauss学习笔记-211openGauss数据库运维-高危操作一览表各项操作请严格遵守指导书操作,同时避免执行如下高危操作。211.1禁止操作表1中描述在产品的操作与维护阶段,进行日常操作时应注意的严禁操作。表1禁用操作操作名称操作风险严禁修改数据目录下文件名,权限,......
  • 【侯捷C++面向对象笔记】补充2-pointer-like & function-like class
    关键词:仿函数pointer-like:将一个类设计得像指针一样,通常通过重载*和->操作符实现。function-like:将类的成员设计得能像函数一样使用,通过重载()操作符实现。TipDemo应用:智能指针注意:->符号在作用一次后,会继续作用下去(不同于*号)Foof(*sp):f为一个Foo对象本体,使用时f.m......
  • 【侯捷C++面向对象笔记】补充3-template
    关键词:类模板,函数模板,成员模板,模板特化“泛化”和“特化”TipDemo类模板定义时需要显式地指定类型名。函数模板定义时编译器自动进行实参推导类型(但不提供隐式转换)。成员模板:模板中还包含模板模板(全)特化格式:template<>尖括号内为空模板偏特化(partia......
  • 【侯捷C++面向对象笔记】补充4-object model
    关键词:虚函数表,动态绑定,多态每个对象都维护自己的虚表指针,指向类的虚函数表。(所以对象的size比其包含的所有数据size多4,即虚指针大小)➡️动态绑定:(多态的实现原理)通过指针p找到对象c的vptr通过vptr找到classC的vtbl在vtbl中找到第n个虚函数并调用➡️子类调用父类函数隐......
  • 小书匠发布笔记到博客园
    小书匠发布笔记到博客园小书匠博客园发布要把小书匠里面写的markdown笔记发布到博客园中,需要做如下的设置。一、设置元数据下面是我这个笔记的元数据设置。这里面,title是笔记的标题,tags是笔记的标签,renderNumberedHeading:true表示自动对标题进行编号,grammar_cjkRuby:t......