感觉自己学 SAM 的时候总有一种似懂非懂、云里雾里、囫囵吞枣、不求甚解的感觉,是时候来加深一下对确定性有限状态自动机的理解了。

  1. 从 SAM 的定义上理解:SAM 可以看作一种加强版的 Trie,它可以高度压缩一个字符串的子串信息,一条从根出发到终止结点的路径对应了原串的一个后缀,而任意一个从根出发的路径对应了原串一个子串。子串和从根出发的路径一一对应。在这种的理解下,每一个结点的含义并不是固定的,它到底对应哪个子串取决于那条路径是怎么到达它的;而边有着确定的含义。

  2. 从 Parent Tree 的角度去理解连边的含义:显然等价类的个数是 \(\Theta(n)\) 级别的,并且两个不同等价类的 \(\texttt{Endpos}\) 集合要么无交集,要么相包含,因此可以建出一个由 \(\texttt{Endpos}\) 集合的包含关系连结而成的树——Parent Tree,它的连边——后缀链接,若是向下看,是在一个等价类的前面加上一个字符,从而分成若干的其他等价类;向上看,它是指向包含当前集合的最小的集合;而后缀自动机的连边是在一个等价类的后面加上一个字母,看看它会指向谁,显然对于同一个添上的字母,这个指向是唯一确定的。

  3. 从结点的含义去理解:虽然一个点它并不一定对应哪一个子串,但是它对应的若干子串一定有一个共同的 \(\texttt{Endpos}\),也就是说,若长的那个存在了,短的那些一定是它的子串;短的出现了,那么它向前一些一定是那些长的。由于上面的结论,"本质"不同的子串的个数是线性的,因此我们建立这样一个自动机,其中的每一个结点都对应了一种子串。这也是为什么 Parent Tree 的结点与 SAM 的结点一一对应。

  4. 关键在于上面的这些理解是同时成立的,可以把 Parent Tree 上获得的信息用作 SAM 信息的补充;两者相得益彰,互相成就。

来看一道例题:P3975 [TJOI2015]弦论,大意是多次询问一个串的字典序第 \(k\) 小子串,分为要求本质不同和不要求本质不同的两种。

显然我们只需要使用类似二叉查找树的那种方法就行了,因此问题转化为了求一个结点后面有多少种子串;如果要求本质不同,那么就在自动机上拓扑 DP 一下就行了,因为自动机会自动合并本质相同的子串;如果不要求呢?那么就需要结合第三种理解,我可以在后缀树上求出每个节点对应的子树内叶子节点出现次数的总和,显然这个就是这个结点的出现次数——一个等价类的出现次数等于它的孩子们出现次数的和,而叶子节点的出现次数一定是和 SAM 一起建出来的。而一个结点的后面的出现次数 \(f_x = sze_x + \sum\limits_{t:ch_x}f_t\),这个由加法原理得到。这就引出了一个点的 \(\texttt{Endpos}\) 集合大小的另一个含义——若从根遍历到这个结点的路径,它究竟有多少种结束方式

main():
for (int i = 1; i <= cnt; i++) c[len[i]]++;
for (int i = 1; i <= cnt; i++) c[i] += c[i - 1];
for (int i = 1; i <= cnt; i++) a[c[len[i]]--] = i;
for (int i = cnt; i; i--) if (t)
sze[fa[a[i]]] += sze[a[i]]; else sze[a[i]] = 1;
sze[1] = 0; for (int i = cnt; i; i--)
{
f[a[i]] = sze[a[i]]; for (int j = 0; j < 26; j++)
f[a[i]] += f[ch[a[i]][j]];
}

这里提到一件争议: P2336 [SCOI2012]喵星球上的点名 这题 @fighter_OI 的题解下由两位金钩大佬提出了意见——一个说这个可以被卡到 \(n\sqrt n\),一个说这个可以优化到 \(n\log n\);事实上,我认为这个已经是线性的了,不存在什么 \(n\sqrt n\) 什么 \(\log\) 的问题。我们看这样的代码:

inline void update1(int x, int y)
{
for (; x && las[x] != y; x = fa[x]) sze[x]++, las[x] = y;
} inline void build()
{
.....blabla
tot = 0; for (int i = 1; i <= n; i++)
{
for (int j = 1, p = 1; j <= l1[i]; j++) update1(p = ch[p][s[++tot]], i);
for (int j = 1, p = 1; j <= l2[i]; j++) update1(p = ch[p][s[++tot]], i);
}

虽然这样的代码看似是在暴力,看似可以被卡,但是实际上,它只是补充不漏地遍历了每个子串的所有“本质”不同子串,“不同字串” 的个数又是线性的,所以总的复杂度是 \(\sum|S_i|\),依然是线性的。甚至改成这样也一样能过:

inline void update1(int x, int y)
{
for (; x; x = fa[x]) if (las[x] != y) sze[x]++, las[x] = y;
}

事实证明,对于比较难以维护的信息,我们完全可以遍历一个串的每个“子串”来暴力添加上信息,这么做的复杂度依然是和输入量呈线性的。

UPD:下面的这种写法是错误的,是 \(O(\frac 14 n^2)\) 级别的(用 000000001111111这样的串卡掉),而前面的写法是线性的。

对 SAM 和 PAM 的一点理解的更多相关文章

  1. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

  2. 对socket的一点理解笔记

    需要学web service,但是在视频中讲解到了socket套接字编程.以前貌似课上老师有提过,只是没用到也感觉乏味.现在遇到,自己看了些博客和资料.记录一点理解,不知正确与否. 首先说这个名字,叫 ...

  3. iOS 的一点理解(一) 代理delegate

    做了一年的iOS,想记录自己对知识点的一点理解. 第一篇,想记录一下iOS中delegate(委托,也有人称作代理)的理解吧. 故名思议,delegate就是代理的含义, 一件事情自己不方便做,然后交 ...

  4. 关于web开发的一点理解

    对于web开发上的一点理解 1 宏观上的一点理解 网页从请求第地址 到获得页面的过程:从客户端(浏览器)通过地址 从soket把请求报文封装发往服务端   服务端通过解析报文并处理报文最后把处理的结果 ...

  5. angular.js的一点理解

    对angular.js的一点理解 2015-01-14 13:18 by MrGeorgeZhao, 317 阅读, 4 评论, 收藏, 编辑 最近一直在学习angular.js.不得不说和jquer ...

  6. RxSwift 入坑好多天 - 终于有了一点理解

    一.前言 江湖上都在说现在就要赶紧学 swift 了,即将是 swift 的天下了.在 api 变化不大的情况下,swift 作为一门新的语言,集众家之所长,普通编码确实比 oc 要好用的多了 老早就 ...

  7. rt-thread中动态内存分配之小内存管理模块方法的一点理解

    @2019-01-18 [小记] rt-thread中动态内存分配之小内存管理模块方法的一点理解 > 内存初始化后的布局示意 lfree指向内存空闲区首地址 /** * @ingroup Sys ...

  8. rt-thread中软件定时器组件超时界限的一点理解

    @2019-01-15 [小记] 对 rt-thread 中的软件定时器组件中超时界限的一点理解 rt_thread_timer_entry(void *parameter)函数中if ((next_ ...

  9. mycat的schema.xml的个人的一点理解

    官方文档里讲的详细的部分的我就不再赘述了,我只是谈谈我自己的理解 刚开始接触mycat,最重要的几个配置文件有server.xml,schema.xml,还有个rule.xml配置文件 具体都是干啥用 ...

随机推荐

  1. programmercarl——数组——二分查找

    二分查找,在经过: 34--https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-arr ...

  2. 【UE4 C++ 基础知识】<14> 多线程——AsyncTask

    概念 AsyncTask AsyncTask 系统是一套基于线程池的异步任务处理系统.每创建一个AsyncTas,都会被加入到线程池中进行执行 AsyncTask 泛指 FAsyncTask 和 FA ...

  3. NOIP模拟84(多校17)

    T1 宝藏 解题思路 考场上一眼出 \(nlog^2\) 做法,然后没看见是 1s 3e5 的数据,我竟然以为自己切了?? 考完之后尝试着把二分改为指针的移动,然后就过了??或许是数据水吧,感觉自己的 ...

  4. 2021.6.29考试总结[NOIP模拟10]

    T1 入阵曲 二位前缀和暴力n4可以拿60. 观察到维护前缀和时模k意义下余数一样的前缀和相减后一定被k整除,前缀和维护模数,n2枚举行数,n枚举列, 开一个桶记录模数出现个数,每枚举到该模数就加上它 ...

  5. C++链表常见面试考点

    链表常见问题: 单链表找到倒数第n个节点 用两个指针指向链表头,第一个指针先向前走n步,然后两个指针同步往前走,当第一个指针指向最后一个节点时,第二个指针就指向了倒数第n个节点. 判断链表有没有环 快 ...

  6. Java I/O框架 - 总结概述

    总结 以下需要重点掌握: 字节流,以下读取结束全部返回-1 字节节点流-访问文件 FileInputStream/FileOutputStream 可以读取任意文件 可以复制图片 读取字符String ...

  7. ansible模块及语法

    常用模块详解 模块说明及示例: 1.ping模块ping模块 主要用于判断远程客户端是否在线,用于ping本身服务器,返回值是changed.ping示例 ansible clu -m ping 2. ...

  8. 设计模式学习-使用go实现原型模式

    原型模式 定义 代码实现 优点 缺点 适用场景 参考 原型模式 定义 如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复 ...

  9. 『学了就忘』Linux基础命令 — 38、Linux中光盘的挂载

    目录 步骤一:创建一个空目录 步骤二:找到光盘的设备文件名称 步骤三:挂载光盘 步骤四:访问关盘中的数据 步骤五:卸载挂载点 问题:挂载点为什么要使用空目录 提示:关于Linux系统中光盘的挂载,我们 ...

  10. 大爽Python入门教程 3-5 习题

    大爽Python入门公开课教案 点击查看教程总目录 1 求平方和 使用循环,计算列表所有项的平方和,并输出这个和. 列表示例 lst = [8, 5, 7, 12, 19, 21, 10, 3, 2, ...