首页 > 其他分享 >SAM学习笔记

SAM学习笔记

时间:2022-11-29 21:45:46浏览次数:62  
标签:nxt SAM 后缀 len 学习 link 笔记 endpos

开个坑,花点时间学学 SAM。

一些概念

1.

SAM 是个自动机,而且是个 DAG。

SAM 有一个初始节点 \(u\),从 \(u\) 出发的任意一条路径都对应原串 \(S\) 的一个子串,且原串 \(S\) 的任意一个子串唯一对应一条 \(u\) 出发的路径。

SAM 的优秀之处在于它压缩了很多相似的后缀信息。

2.

有一个很重要的概念就是 endpos 集合,定义 \(endpos(T)\) 是串 \(T\) 在 \(S\) 中的所有出现位置的结尾下标(从 \(1\) 开始标号)。

我们会发现会有一些 \(T\) 他们的 endpos$是相同的,比如令 \(S=abab\) 则 \(endpos(ab)=endpos(b)\)。

如果两个不同的串 \(T_1,T_2\) 同时在 \(x\) 处结尾出现,那必定有较短的那个是较长的那个的后缀。

所以对于两个串 \(X,Y\),我们假设 \(|X|\le |Y|\),则:如果 \(X\) 是 \(Y\) 的后缀,容易得 \(endpos(Y)\subseteq endpos(X)\),否则 \(endpos(X) \cap endpos(Y)=\empty\)。

我们把所有 endpos 相同的子串拿出来称为一个等价类:考察一个等价类的所有串,按照长度从大到小排序以后记作 \(T_1,T_2,...,T_k\)。

根据上面的内容我们可以发现 \(T_k\) 是 \(T_{k-1}\) 的后缀,而 \(T_{k-2}\) 是 \(T_k\) 的后缀,以此类推。

更一般的,显然所有 \(T_i\) 都是 \(T_1\) 的后缀且 \(|T_i|=|T_1|-i+1\)。

所以一个 endpos 等价类实际上就是若干个后缀压缩成了一起,我们用 \(T_1\) 来代表这个等价类。

3.

SAM 想做到的一点是:每个节点(状态)都代表了一个完整的 endpos 等价类。

在构建 SAM 之前,我们还需要引入一个东西:parent tree。

我们知道一个节点 \(x\) 对应一个等价类:\(T_1,T_2,...,T_k\),我们定义 \(T'\) 是 \(T_k\) 去掉开头字符的结果(也就是所谓的 \(T_{k+1}\))。

定义 \(link(x)\) 是 \(T_{k+1}\) 所属的等价类的对应节点。

显然 \(link(x)\rightarrow x\) 连边会形成一颗 \(u\) 为根的树,这个就是 parent tree。

而且你会发现 \(endpos(T)\) 一定是 \(endpos(T')\) 的真子集。

另外我们可以发现兄弟节点的 \(endpos\) 一定是交集为空的,因为他们没有后缀关系。(比如 \(ba\) 和 \(ca\) 的父亲都是 \(a\),那么 \(endpos(ba)\cap endpos(ca)\) 显然为空)。

而且可以说明的是不同的等价类只有 \(O(n)\) 个,因为这就是一个集合分拆的过程啊。

4.

SAM 就是在线地同时构建出 parent tree 和 DFA 本身。

考虑我们建出了 \(S\) 的 SAM,怎么求 \(S+c\) 的 SAM。

我们本质上只增加了 \(|S|+1\) 个以最后一个字符结尾的子串(也就是此时形成的所有后缀),而且你考虑有些后缀应该是已经出现过的,如果长度为 \(x\) 的后缀出现过了那么长度为 \(y\le x\) 的后缀一定出现过了。

这个 \(x-suffix\) 其实就是 \(link(S+c)\) 啊,因为 \(endpos(S+c)\) 显然就是 \(\{|S|+1\}\)。

所以考虑先求 \(link(S+c)\)。

从 \(link(S)\) 不断跳 parent tree(其实就是从大往小枚举后缀,只不过每次会处理一段连续的性质相似的后缀罢了),当跳到一个点 \(p\),它的 \(nxt(c)\) 已经有值,我们设 \(p\) 所在的等价类,最长的那个串是 \(a\),那么 \(a+c\) 就一定就是最长的出现过了的后缀。

设 \(p\) 跳转到了 \(q\),如果 \(len(q)=len(p)+1\) 那么直接 \(link(S+c)=q\)。这里 \(len(a)\) 表示的是节点 \(a\) 所代表的等价类里最长的串长。

如果不然,也就是 \(len(q)\gt len(p)+1\),那么说明 \(a+c\) 只是 \(q\) 等价类的一个非最长串,你不能让 \(link(S+c)=q\),因为 \(q\) 集合的最长串并不是 \(S+c\) 的后缀。

此时等价类 \(q\) 实质上被分为了两部分:长度在 \((len(p)+1,len(q)]\) 这段的串的 endpos 不变,而 \((len(link(q)),len(p)+1]\) 这些长度的串的 endpos 应该多了一个位置 \(|S|+1\) 啊。

所以这就是两个等价类了,我们新建一个节点 \(r\),那么 \(link(r)\) 变为原先的 \(link(q)\) 而 \(link(q)\) 变为 \(r\) 且 \(len(r)=len(p)+1\)。

然后 \(nxt_r\) 肯定是照搬 \(nxt_q\) 的,而且我们继续从 \(p\) 跳 parent tree:所有 \(nxt(c)=q\) 的都要改成 \(nxt(c)=r\) 了,最后 \(link(S+c)=r\) 完事。

感觉描述起来挺烦的但理解以后并不难记难写。

另外关于求 endpos 啊。我们计算完 \(S\) 对应的节点后,在该节点的 endpos 集合中加入 \(|S|\) 这个数,然后一个节点的真实 endpos 应该是 parent tree 中,所有子树的并,所以可以建出 SAM 以后线段树合并。

一些例题

Luogu 3804 【模板】后缀自动机

求一个串 \(S\) 中所有出现次数大于 \(1\) 的子串的:出现次数 * 长度的最大值。

\(|S|\le 10^6\)。


出现次数大于 \(1\) 也就是 \(|endpos|\gt 1\) 且我们注意到对于一个 endpos 等价类我们其实只关注 \(len(x)\),也就是里面最长的那个串。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN=1e6+10,MAXC=30;
char s[MAXN];
int n;
ll ans;

struct Node{int fa,len,nxt[MAXC];};
namespace SAM{
    Node t[MAXN*2];
    vector<int>e[MAXN*2];
    int sz,lst,siz[MAXN*2];
    void init(){
        rep(i,0,sz)e[i].clear();
        sz=lst=0;
        memset(t[0].nxt,0,sizeof t[0].nxt);
        t[0].fa=-1;t[0].len=0;
    }
    int newnode(){
        sz++;
        memset(t[sz].nxt,0,sizeof t[sz].nxt);
        return sz;
    }
    void extend(int ch){
        int cur=newnode();t[cur].len=t[lst].len+1;siz[cur]=1;
        int p=lst;
        while(p!=-1 && !t[p].nxt[ch]){
            t[p].nxt[ch]=cur;
            p=t[p].fa;
        }
        if(p==-1){
            t[cur].fa=0;
        }else{
            int q=t[p].nxt[ch];
            if(t[q].len==t[p].len+1)t[cur].fa=q;
            else{
                int r=newnode();
                rep(j,0,25)t[r].nxt[j]=t[q].nxt[j];
                t[r].len=t[p].len+1;
                t[r].fa=t[q].fa;

                while(p!=-1 && t[p].nxt[ch]==q){
                    t[p].nxt[ch]=r;
                    p=t[p].fa;
                }

                t[q].fa=t[cur].fa=r;
            }
        }
        lst=cur;
    }
    void build(){
        rep(i,1,sz)e[t[i].fa].push_back(i);
    }
    void dfs(int u){
        for(auto v:e[u])dfs(v),siz[u]+=siz[v];
        if(siz[u]>1)ans=max(ans,1ll*siz[u]*t[u].len);
    }
};
int sz[MAXN*2];
vector<int>res;
int main(){
    
    cin>>(s+1);
    n=strlen(s+1);

    SAM::init();
    rep(i,1,n){
        SAM::extend(s[i]-'a');
    }
    SAM::build();
    SAM::dfs(0);

    cout<<ans;
    return 0;
}

SAM 很强大啊,能干的事情非常多,先咕了。

标签:nxt,SAM,后缀,len,学习,link,笔记,endpos
From: https://www.cnblogs.com/Cry-For-theMoon/p/16936801.html

相关文章

  • 大数据学习之kafka
    kafka是一个分布式的基于发布/订阅模式的消息队列,只要应用于大数据实时处理领域消息队列的两种模式:点对点模式(一对一消费者主动拉取数据,消息收到后消息清除)......
  • Lua学习笔记
    1.注释--单行注释 多行注释--[[--]] 2.变量命名最好不要使用下划线加大写字母作为标示符,因为lua内部的保留字也是这样命名的。Lua不允许使用特殊字符如@,$,和%来定......
  • k8s学习笔记
    1.pv学习mysql-pv.yamlapiVersion:v1kind:PersistentVolume#申明资源是pvmetadata:name:pv-mysql-datadir#pv名称labels:pv:mysql-datadir#pv标签,pvc关......
  • C#设计模式读书笔记之设计模式的设计原则
    设计模式的设计原则:(重要性从上往下排列)开闭原则:对扩展开放,对修改关闭依赖倒转原则:高层模块不应该依赖底层模块,它们都应该依赖抽象;要针对抽象层编程,而不要针对具体类编程。......
  • java学习问题
    1、nacosConnectionrefused:connect由于配置文件配置错误引起的。我的nacos是部署在另一台linux服务器的,yml具体配置如下: ......
  • 注册不到两年半Github标星39k+,吴恩达、李航老师的作品的笔记和代码实现
    2017年11月,我注册了github,现在差不多两年半了,一共收获了约39000star,排名个人用户81。今天,我就对我的github做下介绍,里面的几个仓库,非常适合机器学习和深度学习入门。......
  • 首发:徐亦达教授团队最新发表的两篇机器学习论文
    徐亦达团队在AsianConferenceonMachineLearning的发表了两篇机器学习论文,本人得到徐老师授权在本站发布论文。论文1:RealisticImageGenerationusingRegion-phrase......
  • angr_ctf——从0学习angr(二):状态操作和约束求解
    状态操作angr中提到的状态(state)实际上是一个Simstate类,该类可由Project预设得到。预设完成后,还可以根据需要对某些部分进行细化操作。一个state包含了程序运行到某个阶段......
  • AES算法学习02:原理总结和实现(ECB)
    一原理介绍:其实AES就是对16byte(128bit)数据进行加密的过程。说白了就是把128位通过一系列的变化变成另一个128数据。   这里主要用到2个关键的东西。密钥(key)这个是绝......
  • Android约束布局:ConstraintLayout学习文章记录
    (一)Android新特性介绍,ConstraintLayout完全解析(二)ConstraintLayout完全解析快来优化你的布局吧参考官方文档:​​https://developer.android.com/reference/android/suppor......