zoukankan      html  css  js  c++  java
  • 后缀自动机

    又开坑?

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

    反正还是不懂。找到几个资料,云说好好看论文最好,但是真的看不懂(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

    (就不要吐槽代码丑了)

     

     

    后缀自动机习题合集

  • 相关阅读:
    Mvc+三层(批量添加、删除、修改)
    js中判断复选款是否选中
    EF的优缺点
    Git tricks: Unstaging files
    Using Git Submodules
    English Learning
    wix xslt for adding node
    The breakpoint will not currently be hit. No symbols have been loaded for this document."
    Use XSLT in wix
    mfc110ud.dll not found
  • 原文地址:https://www.cnblogs.com/Macaulish/p/4295486.html
Copyright © 2011-2022 走看看