首页 > 其他分享 >后缀自动机

后缀自动机

时间:2023-01-30 21:23:47浏览次数:49  
标签:last 后缀 len st endpos nq 自动机

后缀自动机的疑难点

代码

void sam_extend(char c) {
  int cur = sz++;
  st[cur].len = st[last].len + 1;
  int p = last;
  while (p != -1 && !st[p].next.count(c)) {
    st[p].next[c] = cur;
    p = st[p].link;
  }
  if (p == -1) {
    st[cur].link = 0;
  } else {
    int q = st[p].next[c];
    if (st[p].len + 1 == st[q].len) {
      st[cur].link = q;
    } else {
      int clone = sz++;
      st[clone].len = st[p].len + 1;
      st[clone].next = st[q].next;
      st[clone].link = st[q].link;
      while (p != -1 && st[p].next[c] == q) {
        st[p].next[c] = clone;
        p = st[p].link;
      }
      st[q].link = st[cur].link = clone;
    }
  }
  last = cur;
}

构造后缀自动机

\(case1\),\(p\)沿着后缀链接树向上跳,涉及到的每个\(p\)没有\(st[p].nxt[c]\)的转移

  • 从后缀链接树的\(last\)状态向上跳,会遍历到长度为\([0,len(last)]\)的所有区间,即这个字符串的所有后缀,若跳到了根节点也没有\(c\)的转移,那么就是整个\(SAM\)中不存在字符\(c\),那么为\(p\)的每一个\(c\)转移都指向新加入的那个点,后缀链接树上,将\(longest(last)+c\)的后缀链接指向根节点,这是独一无二的\(endpos\)

\(case2\),\(p\)沿着后缀链接树向上跳,找到一个\(p\)到\(q\)的转移\(c\),并且满足\(len(p)+1=len(q)\)

  • 后缀链接树向上找,找到的每个状态都是\(last\)的后缀,如果这个状态\(p\)通过转移\(c\)正好能够走到\(q\),并且\(q\)是它所属的\(endpos\)等价类中最长的,那么显然\(q\)不会在前方相比\(p\)突出一块,会与\(p\)对齐,而\(p\)是\(last\)的后缀,\(p+c\)即\(q\)必然是\(last+c\)的后缀,所以\(last+c\)的\(endpos\)必然是\(q\)的一个真子集,而由于我们找的是顺着后缀链接树的第一个满足条件的\(p\),它必然是最长的,又由于\(q\)也是它对应的\(endpos\)等价类中最长的,所以\(last+c\)不断地删头\(endpos\)首次发生变化必然是删到\(len(q)\)时,所以直接相连是没有问题的

  • 为什么找到一个\(p\)就停下来?因为如果我们继续向上跳,跳到的必然是\(p\)的后缀,记为\(p'\),所以它通过转移\(c\)对应的状态\(q'\)也一定是\(q\)的后缀,那在后缀链接树上,\(q'\)一定是\(q\)的父亲,由于后缀链接树上,儿子的\(endpos\)集合是父亲的真子集,所以在\(q\)的儿子接上位置\(n\)之后,由定义,它的所有的父节点的\(endpos\)集合都顺水推舟、水到渠成的拥有了位置\(n\),所以只连一次就符合了\(SAM\)的定义

\(case3\)那\(len(p)+1<len(q)\) 怎么搞呢

  • 我们发现,不同于上一个情况,我们的\(q\)状态的前端和\(p\)状态相比突出了一块,而这时候我们找到的\(last\)的最大的\(endpos\)集合不同的后缀是\(p\)而不是\(q\),因此增加\(c\)之后,仅仅有\(p+c\)的状态的\(endpos\)集合增加了\(n\),我们记这个状态为\(nq\),可以发现它实际上是\(q\)状态中最短的子串吗,所以合着\(q\)状态中只有最小的\(nq\)的\(endpos\)增加了。怎么办呢?我们考虑将q状态拆开,将到\(q\)的转移\(c\)全部指向\(nq\),与此同时,\(q\)的所有转移,\(nq\)也一定继承。这很好理解,因为\(nq\)实际就是\(q\)的后缀,在它后面加什么\(defg\cdots\) ,所指向状态的\(endpos\)都不会受影响,\(nq\)与\(q\)还是一家人(一家人再怎么打骂起分歧也不会分开),所以这么连肯定也是对的

  • 下面考虑后缀链接树怎么搞,首先一个显而易见的结论,\(nq\)的\(endpos\)集合仅仅只比\(q\)的\(endpos\)集合多了一个元素\(n\),这里好办,让\(nq\)代替\(q\)的位置,然后把\(q\)和\(last+c\)的后缀链接指向\(nq\)(为什么能取代,儿子取代父亲的位置不是恒河里吗doge),然后我们通过后缀链接树向上跳,如果发现\(st[p].nxt[c]==q\),那么就在\(SAM\)上把这个\(p\)的转移\(c\)直接连到\(q\)上("将到\(q\)的转移\(c\)全部指向\(nq\)",对应的就是上文的这句话),因为这时跳到的\(p+c\)的\(endpos\)集合包含\(n\),它显然不应该指向\(q\)。如果发现\(st[p].nxt[c]\neq q\),这句话是什么意思呢?就是我们的\(q\)已经足够小了,此时跳到的\(p\)是起初\(p\)的后缀,再增加一个字符\(c\)会形成一个更大的\(endpos\)集合,增加字符后形成的\(q'\)显然也是\(nq\)的后缀,接着向上跳形成的\(q''\)也同样是\(nq\)的后缀。所以我们发现,再往上走都是\(q\)的祖先,而由于\(nq\)的存在,上面所有的状态的\(endpos\)显然都包含有\(n\),这时\(p\)再向\(q',q'',\cdots\)连边就没有一个状态两种\(endpos\)集合的情况了,所以它是对的

复杂度证明

  • 空间复杂度显然正确,因为每一次插入字符时,\(Sam\)的状态数只会增加两个,因此为\(O(n)\),上界为2n-1,在形如字符串\(abbbb\cdots\)时取到,这就是为什么\(SAM\)的空间要开两倍

  • 时间复杂度,分两种情况讨论

\((1).\) 考虑连续的转移,我们假设能连续就连续,总共就不超过\(2n-1\)个状态,那么将这些点连续地连起来最多也就\(2n-2\)条边,实际上,极限情况\(abbbb\cdots\)也达不到\(2n-2\)

\((2).\) 考虑不连续的转移,令当前不连续的转移为\((p,q)\),字符为\(c\),我们设从起始点到\(p\)的最长路径为\(u\),\(q\)到任意终止点的最长路径为\(w\),那么\(u+c+w\)这个东西显然是原串的一个后缀,而一个长为\(n\)的串最多就有\(n\)个后缀,\(SAM\)中又不会出现重复的子串,因此不连续转移的个数最多就是\(n-1\)

标签:last,后缀,len,st,endpos,nq,自动机
From: https://www.cnblogs.com/starroadxyz/p/17077259.html

相关文章

  • (AtCoder Beginner Contest 287)(D 字符串前后缀合并匹配)(E Trie求最长公共前缀(LCP)
    D-MatchorNot(字符串前后缀合并匹配)题目大意:给定两个字符串S和T,对于每个x=0,1,2...|T|求S的子串(前x个字符和后|T|-x个字符在不改变顺序的情况下组成)是否与T相......
  • upload-labs pass3,phpstudy中修改httpd.conf后无法解析.php3后缀。phpstudy中64与32系
    问题解决参考自:https://www.likecs.com/show-965809.html 注意:VC运行库(V14-x64)版本必须与Apache、PHP版本相同;VC就是MicrosoftVisualC++,可以通过控制面板查看否则......
  • 关于AC自动机的一些理解 || Luogu3121 & 4824 Censoring - 哈希 - AC自动机
    题目链接:https://www.luogu.com.cn/problem/P3121(4824)题解:4824是CensoringS,只需要对单模式串进行操作,3121需要对多模式串4824开一个前缀hash数组,每次扫到当前点......
  • 汇编语言源码文件后缀.S
    汇编语言源码文件后缀名是.s(不区分大小写,一般是根据约定,比如每个公司要求不一样)但一定是s结尾。   来源:B站《韦东山_嵌入式Linux_第一期ARM裸机实战视频教程_......
  • AC 自动机学习笔记
    前置知识:KMP,trie。一.自动机这里的自动机都指有限状态自动机(DFA)。一个DFA可以理解为一张有向图,由有限的状态(点),字母表,转移函数(边),开始状态与终止状态(起点,终点)组成。AC......
  • 「学习笔记」后缀自动机与广义后缀自动机
    本文仅为方便理解,没有具体证明过程,推荐理解主要原理后去阅读详细证明过程。后缀自动机是一个处理字符串子串相关问题的优秀的算法。引入首先来考虑这样一个问题:如何存......
  • 「学习笔记」后缀数组
    后缀数组,即\(sa\)数组,代表的是一个字符串所有后缀按照字典序排序后,排名第\(i\)的后缀的开头位置。举个例子:aabaaab的所有后缀有:编号前缀1aabaaab2......
  • 【置顶】大慈大悲·电子木鱼·功德自动机
    24h无间断膜拜区%%%%%%%%%%%%%%%%%%%%%%%%%%%%%orzorzorzorzorzorzorzorz......
  • AC自动机模板
    P3808【模板】AC自动机#include<bits/stdc++.h>usingnamespacestd;constintM=1e6+5;intch[M][26],cnt[M],fail[M],tot;voidinsert(char*s){//字典树的建......
  • SA后缀数组
    SA后缀数组sa数组:sa[i]=x表示对于所有的后缀进行排序(字典序)后,得到排名为i的以第x个字符开头的后缀rk数组:rk[x]=i,是对于所有的后缀进行排序(字典序)后,得到以第x个字符......