zoukankan      html  css  js  c++  java
  • 后缀自动机复习笔记

    嗯,远古时期学过sam,然后半年没写忘掉了,只记得大致是个啥玩意,来写个笔记搞一搞

    sam及后缀树构造

    设原串为(S)

    1.需要知道的是sam不是后缀树,这是个类似于如果中间没有状态会被压起来的一个dfa,但是sam的fail树实际上就是(S)反串的后缀树

    2.先来看反串的后缀树,没被压缩过的实际上就是把所有后缀(suf_i)暴力插入(trie),这样状态数是(O(n^2))的,同时因为trie可以识别前缀,所有后缀的所有前缀即为子串

    记每个非空子串为(s_j),那么每个子串都在(S)中有匹配,对应的匹配一次结束位置的集合为(endpos_j)

    明显如果对于同一(S)内的(endpos_jcap endpos_k ot = empty),一个子串一定为另一个子串的后缀

    3.每个点(u)对应一个状态,每个状态对应多个子串(s_j),因为这些子串(s_j)(endpos_j)交于(u),所以对于一个点(u),其上的

    (forall i,j, endpos_i)(endpos_j)一定具有子集关系,因此状态数是(O(n))

    4.fail边奥妙重重,考虑当前点(u)(endpos),取一个(endpos)包含了当前状态的最长串的最长后缀,那么fail边连向的就是这个最

    长后缀的(endpos)中另外一个状态点(fail_u),既然(vert s[fail_u]vert leq vert s[u]vert),所以(u)(endpos)一定是(fail_u)对应状态的(endpos)的子集


    hhh上面是在没学透的情况下的扯皮,先来看怎么构建这玩意。

    直接放代码吧,反正是给自己看的玩意。

    char S[N];
    int tot = 1, last = 1;
    struct SAM {
      int son[26], len, fail;
    } t[N];
    void ins(int c) {
      int p = last, np = ++tot;
      t[np].len = t[p].len + 1, last = np;
      for ( ; p && (!t[p].son[c]); p = t[p].fail) t[p].son[c] = np;
      if (!p) {
        t[np].fail = 1;
      } else {
        int q = t[p].son[c];
        if (t[q].len == t[p].len + 1) {
          t[np].fail = q;
        } else {
          int cur = ++tot;
          t[cur].fail = t[q].fail, 
          t[cur].len = t[p].len + 1;
          for (R int o = 0; o <= 25; o++)
            t[cur].son[o] = t[q].son[o]; 
          //*t[cur].son = *t[q].son;
          while (p && t[p].son[c] == q) {
            t[p].son[c] = cur;
            p = t[p].fail;
          }
          
          t[q].fail = t[np].fail = cur;
        }
      }
    }
    

    设新插入的字符节点为(np),初始节点为(init)

    对于每个新插入的字符(c),都需要在(last)的基础上暴跳(fail) 直到暴跳到的节点(p)(son[c])产生了冲突或者(p)(null)为止

    同时对于路径上的每个点的(son[c])都指向(np)

    1.如果(p)(null),那么(fail[np] = init)

    2.如果(p.son[c])有冲突
    1.这个状态是连续的即(len[p] + 1 = len[p.son[c]]),不需要做出干涉

    2.这个状态是不连续的,也就是中间隔了一段直到(p)前缀都相同的字符串然后(c)转移的指针直接指了过去,现在后缀又匹配到了(p),并且要求有一个真正的后缀,也就是(p+c),所以需要新建一个节点来替代这个(p.son[c])(同时也相当于新建了一个等价类),同时复制除了(len)以外的所有信息,然后把原来指向(p.son[c])的且具有相同后缀的点的(son[c])指向这个新建的节点。这个时候我们注意到(fail[p.son[c]])变成了这个新建的节点
    为什么(fail[p.son[c]])会变成这个新建的节点?因为这个时候p+x的后缀和p匹配,因此(p+c)成为了(p+x+c)的子串,(p+c)也变成了(p+x+c)(last+c)的父亲,这个时候(endpos)集合一定是子集关系,联想到到(fail)树的本质是反串的后缀树,相当于后缀树边新增分叉。

    这是(fail[q])改为(new)节点的原因,也可以说他们有相同后缀(p+c是p+x+c的后缀)

    关于后缀树

    hhhh我们知道(fail)树实际上就是反串的后缀树,且一个点的(endpos)相当于其子树中叶子的(endpos)的并集

    考虑啥时候有(endpos)

    明显每个前缀会拥有自己的(endpos),每个前缀插入后的状态的fail树链上的祖先也会吃到这个(endpos),也就是这个前缀的所有的后缀都会吃到(endpos),考虑在冲突的时候新建的节点(跨越了一整个子串裂出来的那个点)为啥没有(endpos),这个子串明显是一个前缀的后缀,因此不应该主动产生(endpos),这个时候也相当于新建一个等价类。

    举个栗子:

    在这里插入图片描述
    点z明显是新等价类的代表,每个后缀作为一个非独立等价类(即后缀的后缀可以产生更大的等价类)。

  • 相关阅读:
    垃圾收集器与内存分配策略(二)之垃圾收集算法
    组合与继承
    垃圾收集器与内存分配策略(一)之对象存活判断
    虚拟机中对象的创建、内存布局、访问
    Java运行时数据区域划分
    Java操作excel表格
    位段
    sh -s用法
    ubutu14.04选中文本就会删除问题解决
    java容器-List
  • 原文地址:https://www.cnblogs.com/cjc030205/p/11638097.html
Copyright © 2011-2022 走看看