@算法 - [email protected] 后缀自动机

目录

  • @0 - 参考资料@
  • @0.5 - 引言@
  • @1 - what is [email protected]
    • @自动机@
    • @DAWG@
    • @终点集合 [email protected]
    • @后缀链接 与 parent 树@
  • @2 - how to build [email protected]
    • @理论@
    • @代码@
  • @3 - where can it [email protected]

@0 - 参考资料@

Menci‘s Blog 的讲解

陈立杰冬令营的课件

@0.5 - 引言@

后缀自动机(Suffix Automaton,简称 SAM)概念比较抽象,构造方法比较抽象,复杂度证明也比较抽象。所以对于初学者体验感极差是很正常的。

但是关键是,这个东西应用倒是很广泛。

@1 - what is [email protected]

@自动机@

自动机嘛,就是扔进去一个字符串,判断它是否具有某个特征,具有返回 true,不具有返回 false。

我们所说的后缀自动机就是用来判断某一个字符串是否为给定的串 S (称为母串)的后缀。

@[email protected]

你可以把后缀自动机看成一个有向无环图。具体来讲是这样的:

这个图有一个起点(初始状态),同时每条转移边上有一个字符。

从起点出发到达某一结点的一条路径对应着一个字符串(即将路径上所有转移边的字符依次连起来),我们称这个结点表示这一个字符串。显然一个结点可以表示多个字符串。

假如某结点表示的所有字符串都是母串的后缀,我们就标记这个结点。被标记的结点集合称为结束状态集合

对于某一个字符串 T,我们从起点出发,在第 i 次沿着 Ti 这条边走,最终走到的终点如果被标记了,就返回 true(称 T 被自动机识别)。

我们称这个有向无环图为 DAWG

对于某一个字符串,比如 “aabbabd”,很容易想到一个非常简单暴力的 DAWG 构造方法(结束状态集合为叶子结点集合):

【图片取自陈立杰的课件】【好像从 root 出发的 “bbd” 所对应的叶子节点是不需要的?还是我理解有误?】

然而这样点数为 O(n^2) 的。我们考虑简化这个自动机。

假如我们已经识别了“abb”,接下来如果想要识别成功,则我们必须要经过 ‘a‘, ‘b‘, ‘d‘ 三条边。

假如我们已经识别了“bb”,接下来如果想要识别成功,则我们也必须要经过 ‘a‘, ‘b‘, ‘d‘ 三条边。

两者是一样的,所以我们完全可以将两个结点合为一个结点。

更进一步的,我们想要知道怎样的结点可以合为一个结点。

@终点集合 [email protected]

为了统一,以下字符串的下标从 0 开始。

定义(1):我们定义字符串 T 的终点集合 end-pos(T),为 T 在母串 S 中所有出现位置的右端点构成集合(因为是右端点,课件中又称为 right(T))。

例如对于母串 S=“aabbabd”:end-pos(“a”) = {0, 1, 4},end-pos(“ab”) = {2, 5},end-pos(“abba”) = 4。

当后缀自动机中的某两个结点的 end-pos 相同时,在它后面加字符(相当于访问它的出边)也是相同的,所以它们的出边集合相同,就可以将它们合并为同一个结点。

当一个后缀自动机上不存在 end-pos 相同的结点时,则称这个是最简状态后缀自动机

这里有一些性质:

性质(1):如果 end-pos(T1) = end-pos(T2),则要么 T1 是 T2 的后缀,要么 T2 是 T1 的后缀。

这个证明比较显然,因为 T1 和 T2 的右端点相同。

性质(2):end-pos(T1) 与 end-pos(T2) 如果有交集,则其中一个是另一个的子集。

如果有交集,则说明某一个地方 T1 与 T2 有相同的右端点,故其中一个是另一个的后缀。

性质(3):end-pos 相同的所有字符串,长度一定构成连续的区间。

这个说起来有点麻烦,还是举个例子解释一下:

对于母串 S=“aabbabd”,end-pos(“bb”) = end-pos(“abb”) = end-pos(“aabb”) = 3。这些串的长度分别为 2, 3, 4,构成了连续区间 [2, 4]。

证明可以利用性质(1),(2),反证一下。此处不再赘述。

定义(2):我们称一个结点 s 能够表示的最长字符串长度为 max(s),结点 s 能够表示的最短字符串长度为 min(s)。则根据最简后缀自动机的定义与性质(3),结点 s 能够表示 min(s) ~ max(s) 内的所有结点,且这些结点呈后缀关系。

比如对于刚刚那个例子,max(s) = 4,min(s) = 2。

@后缀链接 与 parent 树@

为了线性构造后缀自动机,我们还需要一些东西。

定义(3):若对于结点 s,结点 t 表示的所有字符串是结点 s 表示的所有字符串的后缀,且 min(s) = max(t) + 1,则 s 向 t 连一条单向的虚边,称为后缀链接。记 t = fa(s)。

同时可以发现结点 t 还满足 end-pos(s) \(\subset\) end-pos(t)。

还是举例子:假如结点 s 能表示的字符串为 {aabba, abba, bba},则结点 t 可以是 {ba, a}。

定义(4):根据我们刚刚的性质(2),后缀链接一定构成了一棵树,我们称为 parent 树。其中 parent 树的根是 DAWG 的起点。

网上把 parent 树 也叫作 前缀树,不过考虑到大家如果看到在众多后缀中突然出了一个前缀可能有些懵,所以这里就写的不是前缀树。事实上,前缀树这个名字也有它的道理,此处不展开。

@2 - how to build [email protected]

@理论@

【建议在理解了上面说的这些东西过后再来看这一小节,不然你就会像我初学的时候一样晕乎乎的】

接下来我们就来讲讲怎么去构造这样一个后缀自动机。

我们采用的基本方法为增量法进行在线构造,即每次在母串后面加入一个新的字符并维护当前的后缀自动机。

令原母串 S 的后缀自动机中,能够表示整个母串 S 的结点为 lst。则 lst 到树根的路径上的结点 lst, fa(lst), fa(fa(lst)), ..., root 包含了 S 的所有后缀。

考虑对 S + c 建立后缀自动机,这个新串会增加 |S| + 1 个子串。我们需要让 DAWG 识别这些新子串。我们新建一个结点 cur 表示这些新串。

可以发现这些新子串等于原来的后缀 + 字符 c。所以我们仅对 lst, fa(lst), fa(fa(lst)), ..., root 进行操作。令 v1 = lst, v2 = fa(lst), ..., vk = root。

分两类情况讨论:

(1)假如 v1...k 都没有字符 c 这条转移边,则我们直接把 v1...k 向 cur 连字符 c 的转移边,并将 cur 的后缀链接连向 root。

(2)假如 vi 是第一个有字符 c 这条转移边的,可以根据我们上面的性质证明 vi 之后的所有结点一定有字符 c 这条转移边。我们把 vi 之前的结点向 cur 连字符 c 的转移边,这样子 DAWG 的性质就满足了(可以识别这些新串)。我们再设法去满足 parent 树的性质:

【接下来的内容就是最让我自闭的内容了】

我们找到 vj,使得 vj 是最后一个通过字符 c 这条转移边到达 p 的结点,则有 min(vj) + 1 = min(p)。

又因为 i 到 p 存在一条转移边,所以有 max(p) >= max(vi) + 1。

当 max(p) = max(vi) + 1 时,此时 p 所表示的所有字符串都是通过 vi...j 转移过来的,因此都是 cur 的后缀,所以直接 cur 向 p 连后缀链接。

当 max(p) > max(vi) + 1 时,我们新建一个结点 np 使得 max(np) = max(vi) + 1,并将 min(p) 修改为 max(np) + 1。我们想要通过改变一些边使得 np 与修改后的 p 所能表示的字符串并集等于修改前的 p 所能表示的字符串。

将 vi...j 向 np 连边,并将 p 的后缀链接连向 np,再将 cur 的后缀链接连向 np 即可。这样修改过后, parent 树的性质也可以满足了。

@代码@

实际实现中我们可以不存储 min(s),因为 min(s) = max(fa(s)) + 1。

下面这份代码只是展现构造后缀自动机的过程,并无实际作用。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1000000;
struct node{
    node *ch[26], *fa; int mx;
}pl[2*MAXN + 5], *tcnt, *root, *lst;
void init() {
    lst = tcnt = root = &pl[0];
    for(int i=0;i<26;i++)
        root->ch[i] = NULL;
    root->fa = NULL, root->mx = 0;
}
node *newnode() {
    tcnt++;
    for(int i=0;i<26;i++)
        tcnt->ch[i] = NULL;
    tcnt->fa = NULL, tcnt->mx = 0;
    return tcnt;
}
void sam_extend(int x) {
    node *cur = newnode(), *p = lst; lst = cur;
    cur->mx = lst->mx + 1;
    while( p != NULL && p->ch[x] == NULL )
        p->ch[x] = cur, p = p->fa;
    if( p == NULL )
        cur->fa = root;
    else {
        node *q = p->ch[x];
        if( q->mx == p->mx + 1 )
            cur->fa = q;
        else {
            node *cne = newnode();
            (*cne) = (*q); cne->mx = p->mx + 1;
            cur->fa = q->fa = cne;
            while( p != NULL && p->ch[x] == q )
                p->ch[x] = cne, p = p->fa;
        }
    }
}
char s[MAXN + 5];
int main() {
    init(); scanf("%s", s);
    int len = strlen(s);
    for(int i=0;i<len;i++)
        sam_extend(s[i] - ‘a‘);
}

@3 - where can it [email protected]

本节暂缺……因为我还暂时没有写题。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10228328.html

时间: 2024-10-28 09:56:43

@算法 - [email protected] 后缀自动机的相关文章

算法学习:后缀自动机

[前置知识] AC自动机(没有什么关联,但是看懂了会对后缀自动机有不同的理解) [解决问题] 各种子串的问题 [算法学习] 学习后缀自动机的过程中,看到了许多相关性质和证明,但是奈何才疏学浅(lan) 暂时先放着,到有空再更 [算法分析] 后缀自动机和AC自动机和回文自动机的不同点在于 后缀自动机是个DAG,而AC自动机和回文自动机是树 首先理解   endpos 数组,每个子串都有一个endpos数组,表示他在字符串中出现的位置的结束位置 而endpos数组相同的子串,就被称为endpos类

@算法 - [email&#160;protected] matrix - tree 定理(矩阵树定理)

目录 @0 - 参考资料@ @0.5 - 你所需要了解的线性代数知识@ @1 - 定理主体@ @证明 part - [email protected] @证明 part - [email protected] @证明 part - [email protected] @证明 part - 4@ @2 - 一些简单的推广@ @3 - 例题与应用@ @0 - 参考资料@ MoebiusMeow 的讲解(超喜欢这个博主的!) 网上找的另外一篇讲解 @0.5 - 你所需要了解的线性代数知识@ 什么是矩阵

@算法 - [email&#160;protected] 多项式的多点求值与快速插值

目录 @0 - 参考资料@ @1 - 多点求值@ @理论推导@ @参考代码@ @例题与应用@ @2 - 快速插值@ @理论推导@ @(不建议参考的)代码@ @例题与应用@(暂无) @0 - 参考资料@ Cyhlnj 的博客 @1 - 多点求值@ @理论推导@ 假设已知多项式 \(A(x)\),使用 FFT 可以将 \(A(w_n^0)\),\(A(w_n^1)\),...,\(A(w_n^{n-1})\) 的值在 \(O(n\log n)\) 的时间内快速求出. 那么问题来了,假如我现在要求解任

@算法 - [email&#160;protected] 牛顿迭代法的应用——多项式开方,对数,指数,三角与幂函数

目录 @0 - 参考资料@ @0.5 - 多项式平方根@ @1 - 牛顿迭代法@ @数学上的定义@ @对于多项式的定义@ @2 - 牛顿迭代的应用@ @重新推导 - 多项式逆元@ @重新推导 - 多项式平方根@ @多项式对数函数@ @多项式指数函数@ @多项式幂函数@ @多项式三角函数@ @3 - 一些参考代码@(留坑待填) @4 - 算法应用@(留坑待填) @0 - 参考资料@ Miskcoo's Space 的讲解 Picks 的讲解 @0.5 - 多项式平方根@ 已知一个多项式 \(A(x

中了后缀adobe勒索病毒怎么办 恢复方法百分百解密成功[[email&#160;protected]

深圳的一个公司中了后缀是adobe的勒索病毒,全部文件后缀变成了[[email protected]].adobe公司内中了20几台电脑,中毒后,公司领导特别着急,通过深圳的朋友找到我们,经过我们共同的研究与合作,成功恢复所有被加密文件勒索病毒如何预防 :1.及时给电脑打补丁,修复漏洞.2.对重要的数据文件定期进行非本地备份.3.不要点击来源不明的邮件附件,不从不明网站下载软件.4.尽量关闭不必要的文件共享权限.5.更改账户密码,设置强密码,避免使用统一的密码,因为统一的密码会导致一台被攻破,多

actin/phobos后缀勒索病毒处理 百分百解密[[email&#160;protected]

重庆某公司中了后缀是actin的勒索病毒,这种病毒是后缀phobos病毒的变体,具有更高的危害性.公司领导非常重视,通过朋友找到我们,经过我们共同分析,一天内全部处理完成.请大家做好防御措施,备份重要数据,以防中招.**[email protected]].actin .[[email protected]].actin [[email protected]].actin 1.及时断网,防止内网继续传播. 2.及时把没中毒的服务器或pc重要文件拷贝到移动硬盘保存,防止被感染. 3.中毒机器内,中

新后缀actin勒索病毒防御措施[[email&#160;protected]].actin

**5月份以来actin勒索病毒不断侵入电脑,对网络安全造成严重预警,这种病毒类似phobos后缀勒索病毒,大家一定做好防御,对重要文件备份,防止中招,造成严重损失.沈阳一用友公司找到我们,说他们的一个客户中了actin勒索病毒, 数据库全部被加密,数据库信息非常重要,希望我们能帮他们恢复文件,通过我们的共同配合,及时沟通,成功恢复所有文件. [email protected]].actin .[[email protected]].actin [[email protected]].actin

hihoCoder 后缀自动机三&#183;重复旋律6

后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对于所有的K的答案. 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串S.字符串长度不超过 1000000. 输出 共Length(S)行,每行一个整数,表示答案. 样例输入 aab 样例输出

后缀自动机/后缀树

只是笔记罢了,不要看 关于DAWG: 见紫书P390 把后缀自动机上所有节点都设为接受态就形成DAWG,可以接受一个字符串的所有子串. 一个子串的end-set是它在原串w中出现位置(从1开始编号)的右端点集合. 在DAWG中,end-set相同的子串属于同一个状态. 原因没原因,这应该算定义吧? 任意两个节点的end-set要么不相交,要么是包含关系. 原因:在DAWG上走一步,当前end-set的变化是将原end-set中各个元素+1(要去掉超出字符串长度的元素),然后拆分成1个或多个新en