链接放在这里,有点难理解,至少我个人是的。

  后缀自动机是一种有限状态自动机,其功能是识别字符串是否是母串的后缀。它能解决的问题当然不仅仅是判断是不是后缀这种事,跟字符串的连续子串有关的问题都可以往这个方面考虑,毕竟字符串的每个连续字串都可以看做是两个长度不同的后缀去掉他们的公共部分得到的。

  自动机由五个部分组成:alpha:字符集,state:状态集合,init:初始状态集合,end:结束状态集合,trans:状态转移函数。

  定义trans(s,ch)为当前状态为s,读入字符ch后所达到的状态。若不存在此转移,则将转移的结果定义为null,表示不存在的状态。自动机A能识别的字符串就是所有使得trans(init,x)∈end的字符串,令这些字符串组成的集合为Reg(A)。另外,对于自动机中的某一状态s,从s开始能识别的字符串记为Reg(s)。

  考虑字符串“aabbabd”的后缀,一共有7个,简单的想法是可以将这7个后缀构造成一个trie树(ppt里的图好像有问题,多了abbd这条路线),缺点是状态数太多,对于长度为N的字符串,其节点的规模会达到O(N^2),而后缀自动机相比起来就小多了,其状态数是线性的。

  根据aabbabd构造的后缀自动机:

   

  几个关键的概念:

  ST(str):从初始状态init读入字符串str后转移到的状态。

  S:母串。

  Suf:S的后缀集合。

  Fac:S的所有连续子串集合。

  Suffix(a):从位置a开始的集合。

  S[l,r):S中[l,r)这个区间构成的连续子串。

  Right:对于一个在母串中出现过的连续子串,其每次出现的结束位置集合成为该子串的Right集。例如aabbabd中,子串ab出现了2次,其Right集为{3,6}。

  state:后缀自动机的状态,对应一个Right集,一个状态由所有Right集相同的子串构成。对于一个字符串,其连续子串的规模是O(N^2)。在所有的连续子串中,有这样一些子串,它们的Right集合是相同的。仍然以aabbabd为例:子串aabb,abb,bb在字符串中均只出现了一次,Right集均为{4},同时Right集合为{4}的只有这3个子串,这3个子串构成一个状态,含义是只在5这里出现了一次的子串。

  结论:

  1.令r∈Right(s),只要给定子串的长度len就可以确定子串了。

  2.对于某一个状态s,如果长度为l和r的子串都属于此状态,那么位于l和r之间的所有子串也都属于此状态。

  长度在l和r之间的子串都可以看做长度为r的子串的后缀,长度为r的子串每一次出现,那么长度[l,r)也就是更短一些的子串也必定出现了。随着子串的长度从r逐渐减小,子串出现的机会越来越多,当然也可以在一定范围内保持不变。如果到l为止仍然属于同一个状态,能么可以想见[l,r]全部都属于同一个状态。

  那么,状态可以这样简单地理解,如图(长方形代表字符串......):

  一个状态由多个构造相同的分支组成,每个分支包括若干个字符串,这些字符串由前半部分横线阴影所代表的每各个缀加上后边斜线阴影的全部内容构成。对应上一段的内容,横线阴影左边界到斜线阴影右边界的长度相当于r,横线阴影右边界到斜线阴影右边界的长度相当于l。

  令状态s的长度区间为[Min(s),Max(s)]。

  3.状态数和边数是线性的。

  考虑两个状态a,b。他们的Right集合分别为Ra和Rb。若Ra和Rb有交集,假设r∈Ra∩Rb。因为状态是按照Right集合来划分的,所以不同的状态手下的字符串也是不相同的。现在对于r这个位置来说,状态a的字符串和状态b的字符串都在这里出现并结束了,所以,他们至少结尾的一部分是相同的。而a和b手下的字符串是没有交集的,只有一种可能:它们开始的位置各不相同,也就是说,必然满足[Min(a),Max(a)]和[Min(b),Max(b)]是没有交集的。这说明,a和b中的某一个,它手下全部的字符串都是另一个手下任意一个字符串的后缀,哪怕是最短的那个。那么,对于Ra和Rb来说,其中一个是另一个的真子集。

  总结一下两种状态之间的关系:或者不相交,或者一个是另一个的真子集,可以想见,各个状态的Right集合实际上构成了一个树形结构,不妨称为parent树。

  假想一下,每个字符串结尾有一个空字符,对于只有一个空字符的字符串,它的Right集合就是{1,2,3,4......N},这个状态就是根节点。在字符串左边填入不同的字符,相当于增加对字符串的限定,也可以理解为对字符串的分类,a+str是一类,b+str是一类......每一类对应一个儿子节点,也是一个状态,继承了父节点Right集合的一个子集。当然,有的时候,在左边加一个字符并不能分类,连续加上Max(s)-Min(s)个才行,因为当前字符串左边只能加上那个字符,否则在母串中就不存在了。

  那么,树形结构,叶节点的数量为N,总的节点数最多为2*N。

  构造算法:

  首先定义状态节点:

struct Node {
int step;
Node *pre, *nxt[];
void clear() {
step = ;
pre = NULL;
memset(nxt, NULL, sizeof(nxt));
}
}*root, *last;

  step表示此状态手下的字符串的最大长度,pre指向此状态在parent树中的父亲结点。nxt[i]数组指向由当前状态后接一个字符i转移到的状态。这里并没有保存Right集合,因为每个状态节点的Right集合是其后代所有叶子节点的Right集合的并集。root表示初始状态,last表示结束状态。

  每次添加一个字符,并维护当前的数据结构,使之保持我们所希望的性质。

  令当前的字符串为T,新字符为x,T的长度为L。添加了x后新增加了一些子串,他们都是Tx的后缀,而Tx的后缀就是在T的后缀后面添加了一个x。此时需要做的就是将这些字符串分发给不同的状态节点,如果需要的话,可能会新建一个状态节点。

  添加了这个x后,首先新建一个状态:

    Node *np = cur++, *p = last;
np->clear();
np->step = p->step + ;

  其含义是只在这个新的结尾出现了一次的字符串。

  考虑T所有的后缀(Right集合中包含L)所在的状态v1,v2......显然这些状态是parent树中从树叶到树根的一条路径。设v1=p是其中的叶节点,也就是只包含了一个最长的后缀的状态。

  考虑其中一个v的Right集合{r1,r2,......rn=L}在它的后面添加一个x形成新的状态的话,只有S[ri]=x的那些ri是符合要求的。如果从v出发没有标号为x的转移(先不看rn),那么v的Right集合内就没有满足这个要求的ri。由于v1,v2......的Right集合逐渐扩大,所以如果从vi出发有标号为x的转移,那么之后的状态全部都有这样的转移。对于出发没有标号为x的转移v,它的Right集合内只有rn是满足要求的,符合状态np的“只在新的结尾出现了一次”,所以根据转移规则,让它连一条到np标号为x的转移:

    while (p && !p->nxt[w]) {
p->nxt[w] = np, p = p->pre;
}

  循环结束有两种可能:

  1:这些后缀所在的状态中没有任何一个有标号为x的转移,这种情况直接设置np的父节点为root就可以结束了。

    if (p == NULL) {
np->pre = root;
}

  2:循环到某个p时,该状态有标号为x的转移。此时,又有两个分支。设q为trans(p,x)得到的状态。如果Max(q)==Max(p)+1,就可以直接将np作为q的儿子。

        Node *q = p->nxt[w];
if (q->step == p->step + ) {
np->pre = q;
}

  但Max(q)==Max(p)+1并不一定成立,这里我想了很久才想明白。考虑这样一个字符串:

a a b b c c x a a b b c c x b b c c x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

  最后一个x是刚刚添加的字符。在检查到bbcc这个后缀时,其Right集为Rp={6,13,18}。显然这种状态是有标号为x的转移的,转移后的结果是Rq={7,14},但其Max为7。而最后新添加的这个x,显然并不具备aabbccx的结构,否则就成了上面的那种情况。此时不能直接插入,而是需要新建一个状态,Right集为{7,14,19},Max为5。

            Node *nq = cur++;
memcpy(nq->nxt, q->nxt, sizeof(q->nxt));
nq->pre = q->pre;
nq->step = p->step + ;
np->pre = q->pre = nq;

  nq就是新建的状态,基本照搬原来的q的数据,修改的地方:Max,应该的。设置q的父节点为nq,双方都具有尾部的bbccx结构,之前的q在这个基础上多了个aa,所以q是nq的子状态,然后设置np的父节点为nq。

  接下来,向p的祖先方向找,凡是满足p->nxt[w] == q的都将其的父节点改为nq。

            while (p && p->nxt[w] == q) {
p->nxt[w] = nq, p = p->pre;
}

  最后,修改结束状态为np

    last = np;

  完整代码:

#define N 5050
struct Node {
int step;
Node *pre, *nxt[];
void clear() {
step = ;
pre = NULL;
memset(nxt, NULL, sizeof(nxt));
}
}*root, *last;
Node pool[N * ], *cur;
void init() {
cur = pool;
root = last = cur++;
root->clear();
}
void extend(int w) {
Node *np = cur++, *p = last;
np->clear();
np->step = p->step + ;
while (p && !p->nxt[w]) {
p->nxt[w] = np, p = p->pre;
}
if (p == NULL) {
np->pre = root;
} else {
Node *q = p->nxt[w];
if (q->step == p->step + ) {
np->pre = q;
} else {
Node *nq = cur++;
memcpy(nq->nxt, q->nxt, sizeof(q->nxt));
nq->pre = q->pre;
nq->step = p->step + ;
np->pre = q->pre = nq;
while (p && p->nxt[w] == q) {
p->nxt[w] = nq, p = p->pre;
}
}
}
last = np;
}

  一些相关的练习题:HDU5470 3518 4416 5343 5853 4436 4622 5558  4641 1403

  对象实现版:

struct SAM {
int ch[maxn][], fa[maxn], maxlen[maxn], Last, sz;
int root, nxt[maxn], size[maxn];
void init() {
sz = ;
root = ++sz;
memset(size, , sizeof(size));
memset(ch[], , sizeof(ch[]));
memset(nxt, , sizeof(nxt));
}
void add(int x) {
int np = ++sz, p = Last;
Last = np;
memset(ch[np], , sizeof(ch[np]));
maxlen[np] = maxlen[p] + ;
while (p && !ch[p][x]) ch[p][x] = np, p = fa[p];
if (!p) fa[np] = ;
else {
int q = ch[p][x];
if (maxlen[p] + == maxlen[q]) fa[np] = q;
else {
int nq = ++sz;
memcpy(ch[nq], ch[q], sizeof(ch[q]));
size[nq] = size[q];
nxt[nq] = nxt[q];
maxlen[nq] = maxlen[p] + ;
fa[nq] = fa[q];
fa[q] = fa[np] = nq;
while (p && ch[p][x] == q) ch[p][x] = nq, p = fa[p];
}
}
for (; np; np = fa[np])
if (nxt[np] != now) {
size[np]++;
nxt[np] = now;
} else break;
}
};

后缀自动机 (WJMZBMR讲稿的整理和注释)的更多相关文章

  1. 数据结构:后缀自动机 WJMZBMR讲稿的整理和注释

    链接放在这里,有点难理解,至少我个人是的. 后缀自动机是一种有限状态自动机,其功能是识别字符串是否是母串的后缀.它能解决的问题当然不仅仅是判断是不是后缀这种事,跟字符串的连续子串有关的问题都可以往这个 ...

  2. 【整理】如何选取后缀数组&&后缀自动机

    后缀家族已知成员         后缀树         后缀数组         后缀自动机         后缀仙人掌         后缀预言         后缀Splay ? 后缀树是后缀数 ...

  3. 后缀自动机(SAM)

    *在学习后缀自动机之前需要熟练掌握WA自动机.RE自动机与TLE自动机* 什么是后缀自动机 后缀自动机 Suffix Automaton (SAM) 是一个用 O(n) 的复杂度构造,能够接受一个字符 ...

  4. HDU 1403 Longest Common Substring(后缀自动机——附讲解 or 后缀数组)

    Description Given two strings, you have to tell the length of the Longest Common Substring of them. ...

  5. 【Codeforces235C】Cyclical Quest 后缀自动机

    C. Cyclical Quest time limit per test:3 seconds memory limit per test:512 megabytes input:standard i ...

  6. [转]后缀自动机(SAM)

    原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...

  7. Codeforces 235C Cyclical Quest - 后缀自动机

    Some days ago, WJMZBMR learned how to answer the query "how many times does a string x occur in ...

  8. 回文树&后缀自动机&后缀数组

    KMP,扩展KMP和Manacher就不写了,感觉没多大意思.   之前感觉后缀自动机简直可以解决一切,所以不怎么写后缀数组.   马拉车主要是通过对称中心解决问题,有的时候要通过回文串的边界解决问题 ...

  9. Bzoj2534:后缀自动机 主席树启发式合并

    国际惯例的题面:考虑我们求解出字符串uvu第一个u的右端点为i,第二个u的右端点为j,我们需要满足什么性质?显然j>i+L,因为我们选择的串不能是空串.另外考虑i和j的最长公共前缀(也就是说其p ...

随机推荐

  1. @section Scripts{}的使用

    MVC视图中,Javascripts代码被放于下面的Razor代码中(@section Scripts{}). 好处:在视图进行JavaScript编程时,是一个很好的实践,在共享视图(_Layout ...

  2. SQL Server-数据库架构和对象、定义数据完整性

    前言 本节我们继续SQL之旅,本节我们如题来讲讲一些基本知识以及需要注意的地方,若有不妥之处,还望指出,简短的内容,深入的理解,Always to review the basics. 数据库架构和对 ...

  3. 关于XML学习

    XML为知识构架存储语言: http://www.w3school.com.cn/xml/xml_tree.asp 语义网:为计算机构建一个系统的概念网络模型是计算机理解概念和机间信息交流的途径:XM ...

  4. 那些年 IE 下踩过的坑

    1年过去了,换了一个不用兼容IE8一下浏览器的工作了! 1.:before,:after(伪类) 所有主流浏览器都支持 :before 选择器. 注释:对于 IE8 及更早版本中的 :before,必 ...

  5. 常用CSS代码片段常见css bug

    1.禁止文字被选中 .unselectable { -moz-user-select: -moz-none; -khtml-user-select: none; -webkit-user-select ...

  6. CentOS 7添加开机启动服务/脚本

    一.添加开机自启服务 在CentOS 7中添加开机自启服务非常方便,只需要两条命令(以 jenkins 为例):systemctl enable jenkins.service #设置jenkins服 ...

  7. mac 安装卸载python

    第 1 步,删除框架: sudo rm -rf /Library/Frameworks/Python.framework/Versions/x.x第 2步,删除应用目录: sudo rm -rf &q ...

  8. .net 导入Excel

    今天我在做导入Excel的时候遇到了一些问题,顺便说句其实我很少做这方面的!我的需求是导入EXCEL 验证数据正确性 并把数据显示到页面 如有错误信息则弹出来 那具体问题是什么呢? 导入Excel有2 ...

  9. 【hihocoder 1317】搜索四·跳舞链

    [题目链接]:http://hihocoder.com/problemset/problem/1317 [题意] [题解] dfs就能过吧. 在选取的时候; 把选取的这一行,占据的列,列的权值+1; ...

  10. CentOs下安装图形界面

    CentOS6的图形界面对计算机的内存有要求,应该是要大于512M吧,如果不满足这个条件 在安装的时候,图形界面是不会安装的,可以在linux系统安装完毕后,进入命令后再次安装图形界面 安装图形界面有 ...