• 后缀自动机


    又开坑?

    论文看不懂,网上资料又过于简单?……

    反正还是不懂。找到几个资料,云说好好看论文最好,但是真的看不懂(ZZS:我不知道你们看不看得懂clj的课件,因为我看不懂),子陵小孩子也是直接背,kpm大神直接看网上就懂其精髓(orz!!)。还是再去看论文吧……&

    2015.02.18 0:40 在云的帮助下看完了……只能称丽洁姐太神了,明天看能不能整理下……

     

    后缀自动机

     

    学了后缀数组之后,写出了个比较快的版本,然后又听到kpm大神虐完后缀数组又去虐sam了。于是终于在某天决定开坑。(随便坑了kpm大神一个中午和云一天)。

     

    网上资料好多,严谨的看不懂(orz clj),通俗的看了心虚(orz 19世纪30年代的空间的百度空间)。

     

    先上一些网址吧(看多了只会让自己更乱?!)

    http://hi.baidu.com/myidea/item/142c5cd45901a51820e25039?qq-pf-to=pcqq.group

    http://blog.sina.com.cn/s/blog_70811e1a01014dkz.html

    http://blog.sina.com.cn/s/blog_7812e98601012cim.html

    云这样评价clj的论文:至今能找到的最最科学的东西了

     

    所以我还是按照论文说说一个蒟蒻的理解吧

     

    什么是后缀自动机(sam)

        也就是一个能识别字符串s的后缀的自动机。当且仅当sam(x)=true是字符串x是s的后缀(同时sam也可以识别s的所有的字串哦!)

        Sam就是建立一个最简状态后缀自动机。

        定义了很多东西:

            令ST(str)表示trans(init,str)。就是初始状态开始读入字符串str之后,能到达的状态。

            令Reg(A)表示自动机A能识别的字符。由于我们现在建的是字符串s的后缀自动机,所以Reg(A)就是s的所有后缀。(或者这样表示Reg(ST(s))就是s的所有后缀)

     

    分析:

       首先如果某个字符串x是s的字串,那么在这个字符串x后加上一些字符后就有可能成为s的后缀,而且有可能有不同种方法使它成为s的后缀,也就是说它可能是s的多个后缀的共同前缀!

       比如说我们现在建立起s的sam了。然后读入了字符串s1,那么现在sam就走到ST(s1),那么x如果可以被ST(s1)识别,那么x必须是s的后缀,现在走到了ST(s1)可以识别x,那么s1+x也是s的后缀。那么可以说一个状态ST(s1)可以识别的一定是s的后缀。

       现在我们来举个看个例子。

       S=ABBBABBABBBBBABBA(直接用论文)

       其中BBA出现的位置有

           BBABBABBBBBABBA

           BBABBBBBABBA

           BBABBA

           BBA

        也就是BBA一共出现在{ [3,6) , [6,9) , [12,15) , [15,18) }

        我们记右端点为right,也就是right(BBA)={6 , 9 , 15 , 18}

        然后我们发现有些时候有些字符串的right集合一样的,那我们干脆就记在一起,即有个状态f表示所有right相同的字符串的集合,这个集合就记为right(f)吧。

        然后字符串的出现的区间中无论长度如何,从ri往前数不超过li所得到的字符串一定是一样的,比如上面的BBA={ [3,6) , [6,9) , [12,15) , [15,18) },那么6,9,15,18往前数两个字符都是BA。也就是给个right集合,然后再个适合的长度,就可以确定一个字符串了。

        所有right集合相同的字符串构成一个状态f,那么对于一个状态,也比如存在一个长度范围,【l,r】,这个范围内从ri外前数多上个字符所构成的字符串的right集合还是right(f)。我们记为【min(f),max(f)】。

        现在有两个状态a和b,他们的right集合就分别叫Ra和Rb吧。

        如果Ra和Rb有交集,即其中有相同的ri,那么他们的适合长度区间一定不会有交集(否则某个字符串的right即等于Ra又等于Rb但是Ra<>Rb,显然不可能)。但是Ra和Rb又有交集啊?所以,只能是这样——一个是另一个的真子集。比如上面的BBA和BBBA(两个属于不同的状态),可以发现BBBA的right是BBA的right的子集,同时BBA是BBBA的后缀。So,“个串的right集合,要么不相交,要么一个是另一个的真子集。”

        结果可以发现right集合其实构成了一个树(大集合的儿子们是小集合)。而且还可以发现儿子的min一定是爸爸的max+1(如果不是那爸爸的max+1才是儿子min,矛盾嘛)。

     

        P.s 子串的性质。

           首先每个子串都有属于某个状态,求这个子串出现的个数就变成了求这个子串所在状态的right集合的大小。但是维护这个集合的大小有点麻烦,我们想到right集合是个树形结构,right集合就是它的儿子们right的和,而儿子们的right又是孙子们right的和……最后就是right集合就是它的子树中所有叶子节点的right的和,那么直接用dfs序统计不就好了?!

     

    终于到了构造

        怎么做一只sam?

        我们先来看状态间的转移:一个状态a,它的Right(a)={r1,r2,r3,r4,…..,rn},然后它可以沿一条标号为c的边转移到状态b,那么right(b)必须是right(a)中那些s[ri]=c的ri+1组成的集合。即right(b)={ri+1 | s[ri]=c }。

        然后如果a有一条标号为c的边,那么他在树中的父亲f也一定有一条标号为c的边。比如a表示AA,f表示A,那么a有一条编号为C的到它的儿子,那么f中可能有一条C到f的儿子(AC是AAC的子串嘛)。

       

        现在来想怎么做。

        令当前字符串为T,新字符为x,令T的长度为L。

        现在用sam(T)构造sam(Tx)。新增了一些字符,这些字串都是Tx的后缀,而Tx的后缀,就是T的后缀后面加一个x。

        记v1,v2,v3,。。。,vk,v为right集合中包括L的节点,然后这些v必然是从曾孙子到曾祖父,也就是从最小的v1,(right(v1)={L})开始到root的整条路径。

        设加入x后新的节点为np,即ST(Tx)=np,然后right(np)={L+1}(这些都是显然的)。

     

     举个例子:

    如果当前要加入x,然后第一个x的边为vp(也就是AAAAA所在状态),然后q就是AAAAAX所在状态,right(q)={ri+1 | s[ri]=x }={8,23},长度是【1,7】。如果让q直接插入L+1,那么right(q)={8,23,38},但是长度变成了【1,6】。所以不能直接插入。

    AAAAAAXAAAAAAAAAAAAAAXAAAAAAAABAAAAAX

    AAAAAAXAAAAAAAAAAAAAAXAAAAAAAABAAAAAX

    显然如果max(q)=max(vp)+1就没有任何问题啦。

    解释?:(雾)直接插会让长度变短,但是无论多短,都要>=max(vp)+1,如果原来就是max(q)=max(vp)+1,就不用担心长度变短的影响了……

     

    但是max(q)>max(vp)+1怎么破?

     

    接下来考虑节点nq,在转移的过程中,结束位置L+1是不起作用的,所以trans(nq)就跟原来的trans(q)是一样的,拷贝即可。

      (后面觉得丽洁姐讲的挺好的,其实是我太弱了似乎又理解得不那么透彻?)

       

       

    procedure add(x:longint);
    var
      i,new,now,old,more:longint;
    begin
      new:=addpoint;
      now:=last;
      step[new]:=step[now]+1;
      while (now>=0) and (son[now,x]=-1) do begin
        son[now,x]:=new;
        now:=pre[now];
      end;
      last:=new;
      if now<0 then pre[new]:=0
      else
        if step[son[now,x]]=step[now]+1 then
          pre[new]:=son[now,x]
        else begin
          old:=son[now,x];
          more:=addpoint;
          for i:=0 to 25 do son[more,i]:=son[old,i];
          step[more]:=step[now]+1;
          pre[more]:=pre[old];
          pre[old]:=more;
          pre[new]:=more;
          while (now>=0) and (son[now,x]=old) do begin
            son[now,x]:=more;
            now:=pre[now];
          end;
        end;
    end;
    View Code

    (就不要吐槽代码丑了)

     

     

    后缀自动机习题合集

  • 相关阅读:
    python面试
    Python 3.x--使用re模块,实现计算器运算实例
    Python 3.x--模块导入
    Python 3.x--序列化及反序列化
    Python 3.x--装饰器
    Python 3.x--函数的参数问题
    Python 3.x--文件seek、tell的用法
    Python 3.x--字典循环
    Python3.x--文件读写与list
    Python简介
  • 原文地址:https://www.cnblogs.com/Macaulish/p/4295486.html
Copyright © 2020-2023  润新知