• 「SAM」你的名字


      第二次做了

      回过头来看自己第一次写的代码简直一派胡言

      重新看的题解+骚扰Lrefrain解释代码,耗时一整天

      然后终于明白了

      纪念之

      

      给定$S[]$,多次给定$T[],L,R$,询问$T$有多少本质不同的子串是在$S[L...R]$出现过的

      $|S|<=5e5 |T|<=1e6$

      

      首先考虑$L==1,R==n$的情况怎么做,

      一个直观的想法是拿$T$在$S$的$SAM$上跑

      然后沿路打上标记。然而$SAM$上的一个节点代表长度连续的多个串,可能并不能全部跑到

      所以要维护一个变量$plen$表示已匹配的长度,每个节点标记上有$min(plen,x->len)-x->f->len$串被跑到了

      然后再遍历所有跑过的节点累加就行了?

      既然已经注意到每个节点代表的多个子串中,跑到其中一个必定能跑到它的所有后缀

      不应该忽略$fail$树上此节点父链上所有的子串都是其后缀,都可以跑到

      所以每次都暴力跳父链打上$min(plen,x->len)-x->f->len$的标记,喜提$O(n^2)$

      发现跳父链的时候打上的标记全都是$x->len-x->f->len$,即全都包含

      则如果发现当前的x已经是这个标记了就不用再跳了,喜提$O(nsqrt{n})$

      

      为什么这么不优秀,因为被打标记的节点数的上限是和$|S|$有关的,这不好

      不如只用$SAM(S)$维护$plen$,同时在$SAM(T)$上跑并打标记,喜提$O(n)$

      (其实全世界只有我一开始在$S$上打标记,所有人都想到了同时在$T$上跑。)

      

      然后若$L,R$任意,需要得到$SAM(S[L...R])$,但是不好做

      由于$SAM(S)$可以从$root$跑出$S$的所有子串,所以可以仍然依托$SAM(S)$的整体结构

      而用$endpos$集合来检查是否符合$[L,R]$的限制

      考虑怎么用$endpos$集合来达到等效目的,观察用$SAM(S)$做了什么

      1.寻找目前节点$x$有无字符$c$的出边,有就转移过去

        设目前完成匹配的子串为$C[1...plen]$,此时的$endpos$为$p$,

        其实更应该表述为匹配完成前$plen$时有没有出边,因为通过减小$plen$可能使得原本没有出边现在有出边

        若$C[1...plen]+c$仍在$S[L,R]$中,必满足

        $p-plen+1in[L,R]$,$pin[L,R]$

        即$L+plen-1<=p<=R$

        检查$x->son[c]$有无在区间$[L+plen,R]$中的$endpos$即可

        (由于此时$x=x->son[c],++plen$这行代码还未执行,$plen$还是未加字符$c$前的$plen_0$,不等式中的$plen=plen_0+1$)

      2.跳$x$的$parent$

        不论是$SAM(S[L...R])$还是整个串的$parent$树

        $x$的父链都包含了所有的$x->len$个后缀

        而整个串的$SAM$的可能更长一些,但是总的跳父亲次数怎么也不会超过$|T|$

        所以完全可以直接使用,不会遗漏任何出现过的最长后缀就完事了

      3.查询$x$的$len$

        首先思考真的需要查询$x$在$SAM(S[L,R])$的len吗?

        答案是否,只想要$T$在$S[L,R]$能维持最长多长的匹配,只是当$L=1,R=n$时,恰好等于$x->len$而已

        所以接下来求出来的并不是真正的$len$,而是最长匹配长度。

        

        首先在$[L,R]$中得有$endpos$

        考虑其中最大的$endpos$,设此节点所代表的 以这个位置为结尾的子串中 最长的一个为$skyh$

        如果$skyh$的左端点$>L$,则在$[L,R]$中的最长长度就等于整个串的$SAM$中这个节点的$len$了

        (可以理解为这个$endpos$完整地呈现了$x->len$)

        如果左端点$<=L$,则最长长度为$maxendpos-L$,

        (所以所谓“真正的len”和最长匹配长度也只有在$skyh$越过左端点时有一点区别,就当我前边在说垃圾话吧)

      所以做法就是先建出整个串的$SAM$,然后可持久化线段树合并得到$endpos$集合

      检查当前节点(当前长度),如果有出边,转移过去

      否则$--plen$,如果$plen<=x->f->len$,$x=x->f$

      继续检查有无出边

      另一种做法,不枚举最长匹配长度,而是先枚举此长度位于哪个节点。

      检查方法是,将$x->son[c]->f->len+1$代入$plen$,检查$x->son[c]$里有无合法的$endpos$

      如果没有,那最长匹配的串不在这个节点上,跳$parent$(同时将$plen$对$x->f->len$取min)

      否则在这个节点上,$plen=min(x->len,maxendpos-L)$

      

    1     while(!eg(l,R,x,ch)&&x!=root) x=x->f;
    2     j=min(j,getlen(l,R,x));
    3     x=x->c[ch]; ++j;
    4     j=min(j,getlen(l,R,x));
    很显然的是
    1     while(!eg(l,R,x,ch)&&x!=root) x=x->f,j=min(j,getlen(l,R,x));
    2     x=x->c[ch]; ++j;
    3     j=min(j,getlen(l,R,x));
    两种都是正确的

      关于第二种写法

      为什么$x=x->son[c]$后需要重新将$j$对$getlen()$取$min$呢,难道$getlen()$不会同步增加至少1吗

      $getlen=min(x->len,maxendpos-L)$,$x->len$的确至少增加1了,但是$endpos$极有可能缩小,

      而$eg()$是带入了$x->f->len$不可能关注到这一点,只知道$x$节点里有一个$endpos.$但是不可知道是不是原有的最大的$endpos.$

  • 相关阅读:
    java后台保存JSON
    查询树节点及其所有上级节点sql语句
    查询树节点及其所有下级节点sql语句
    Hibernate查询机制使用原生sql语法查询
    SSH框架通过poi导出excel表格
    java通过poi导入excel数据
    各类型日期date的相互转化
    推荐一下我喜欢的软件
    青岛市赛总结——远征石油大学
    My learn of git
  • 原文地址:https://www.cnblogs.com/yxsplayxs/p/13080280.html
Copyright © 2020-2023  润新知