《基本子串结构》阅读笔记

(就是对一些严谨概念的形式化)

定义 3.1(扩展串):将子串 t 左右扩张到最长的 t 使得 tt 的出现次数相同,称为扩展串 ext(t)

引理 3.1:ext(t) 存在且唯一。(注:这里的唯一指唯一的位置 [l,r],而非仅仅字符串唯一)

证明:设 [l1,r1],[l2,r2] 是不同的最长扩展串,则 [min(l1,l2),max(r1,r2)] 也是扩展串,矛盾。

推论 3.1:若 t=[l,r] 的扩展串为 t=[l,r],那么 tt0tt0 的扩展串也是 t
推论 3.2:ext(ext(t))=ext(t)

定义 3.2(等价类):所有 ext(t) 相同的 t 形成一个等价类。

定义 3.3(代表元):t=ext(t)t 称为该等价类的代表元,记做 rep(a)

引理 3.2:每个等价类的代表元唯一。(显然)
引理 3.3:若 t1t2occ(t1)=occ(t2),则 ext(t1)=ext(t2)

证明:t1t2ext(t2)occ(t1)=occ(t2)=occ(ext(t2))text(t2),occ(t)=occ(ext(t2)),故 ext(t1)=ext(t2)

定义 3.4:定义 ts 中第一次出现位置为 s[posl(t),posr(t)]

定理 3.1:以 l,r 为轴建立平面直角坐标系,则对于等价类 a,其元素的 (posl,posr) 构成的点集在平面上形成一个上端与左端对齐的阶梯。

证明:t 的首次出现的位置与 ext(t) 首次出现的位置对应,且由推论 3.1 可知一个点 t 为右下角,rep(a) 为的矩形内的点都属于等价类 a,证毕。

推论 3.3:平面上 1lrn 的点被划分为若干个互不相交的阶梯,每个等价类 a 对应于其中 occ(rep(a)) 个形状相同的阶梯。(注:由引理 3.1 可得同一 a 之间的阶梯也不相交)

定义 3.5(周长):定义一个等价类 a 的周长 per(a) 为其所对应的阶梯行数与列数之和。

Note:以下也将一个等价类称为一块。

定理 3.2:对于每一个等价类 a,在其对应的阶梯中,每一行(r 相同)对应正 SAM 上一个节点,每一列(l 相同)对应于反 SAM 上一个节点,且所有等价类的所有行与正 SAM 的所有节点一一对应,所有列与反 SAM 的所有节点一一对应。“对应”指字符串集合相同。

证明:以下只证明行的部分,列的部分同理。对于一块中的一行,r 不变,occ 不变,因此都在同一个正 SAM 节点上;对于正 SAM 的一个节点,其所有字符串 occ 相同,因此在同一块内,r 相同,因此在同一行中。证毕。

定理 3.3:所有块的周长之和为 O(n)。(即正反串 SAM 的节点数之和)

Note:最后将正反串 parent 树的树边补上去,每行向它右边那个节点连 T0fa 边(注意“右边”那个节点指的是右边那个点在整个结构中对应的正 SAM 节点),每列向下面那个节点连 T1fa 边。

定理 3.4:对于正 SAM 的节点,如果它上方的节点和它属于同一等价类,那么它在自动机上只有一条出边连向该节点;否则,其连向该节点在整个结构中所有出现位置上方的点。

证明:可以直接由定义推出。

至此,我们已经完成整个基本子串结构的构建。原串正反 SAM 以及 parent 树的所有信息都已在该基本子串结构中体现。

代码见文末。


说了这么多,看一些例题吧:

例题 0

题意: 给定一个长为 n 的字符串 sQ 次询问给出正 SAM 上节点 u 和反 SAM 上节点 v,求任意一个同时在两个 SAM 节点中的子串 s[l,r] 或报告无解。n,Q3×106

解法: 建出基本子串结构,找到 uv 所分别对应的行和列,如果二者不在同一等价类内则无解,否则答案即为该行和该列的交点。

点评: 这是基本子串结构最基本的应用:找到 SAM 节点对应的行和列。

例题 1(Uoj577 打击复读

题意: 给定一个长为 n 的字符串 s,第 i 个字符有两个权值 wliwri,定义一个子串 t 的左权值 vl(t) 为其在原串中出现的所有位置的左端点的 wli 之和,右权值 vr(t) 为其在原串中出现的所有位置的右端点的 wri 之和,定义其权值 w(t)wl(t)×wr(t)。定义整个串 s 的“复读程度”为 i=1nj=inw(s[i,j])Q 次修改某个 wli 后查询答案。n,Q5×105

解法: 显然答案是关于所有 wli 的一个线性函数,于是考虑求出这些系数后直接算答案。首先注意到一个子串 s[l,r] 的左权值即为其在反 parent 树上子树内所有含 s[i,n] 的节点的 wli 之和,右权值即为其在正 parent 树上子树内所有含 s[1,i] 的节点的 wri 之和。对应到网格图上,就是在每一个块中,每一行有一个权值 vr,每一列有一个权值 vl,答案即为所有格子“所在行的 vr,所在列的 vl 以及其所在块的出现次数 occ 的积”之和。于是在每个块内对每一行的 vr 做一个前缀和,就能得到每一列的诸多 vl 会乘上的系数,再自顶至底推下去即可。时间复杂度 O(n+Q)AC 代码

点评: 这道题很好地体现了基本子串结构的特点:利用网格图的特点,在每个块内进行前缀和处理信息,同时保留 parent tree 的性质。

例题 2(Uoj 697 广为人知题

题意: 给定一个长为 n 的字符串 s 以及 sm 个子串 s[li,ri] 作为模式串,接下来 Q 次询问,每次询问给出 qli,qri,求所有模式串在 s[qli,qri] 中出现的次数之和。n4×105,m106,Q105

解法:

template<int NN,int Z>
class SAM{
    static constexpr int N=NN*2;
public:
    int OO,tot,n,Lst,ch[N][Z],fa[N],len[N],pre[N],posl[N],posr[N],occ[N],lis[N],tcnt[N],tmp[N],_ffa[__lg(N)+1][N],ffa[N][__lg(N)+1]; vector<int> son[N];
    SAM(int OOO=0) : OO(OOO) { tot=Lst=1; }
    inline void push_back(int c){
        int np=++tot,p=Lst; Lst=np; occ[np]=1; len[np]=++n; pre[n]=np; posr[np]=n;
        while(p&&!ch[p][c]) ch[p][c]=np,p=fa[p];; if(!p) return fa[np]=1,void();
        int v=ch[p][c]; if(len[v]==len[p]+1) return fa[np]=v,void();
        int nv=++tot; memcpy(ch[nv],ch[v],sizeof(ch[nv])); fa[nv]=fa[v]; posr[nv]=posr[v]; len[nv]=len[p]+1; fa[v]=fa[np]=nv;
        while(p&&ch[p][c]==v) ch[p][c]=nv,p=fa[p];
    }
    inline void build(int oo=0){
        For(u,2,tot) _ffa[0][u]=fa[u];; For(i,1,__lg(n)) For(u,2,tot) _ffa[i][u]=_ffa[i-1][_ffa[i-1][u]];
        For(u,1,tot) For(i,0,__lg(n)) ffa[u][i]=_ffa[i][u];
        For(u,2,tot) son[fa[u]].push_back(u);;
        function<void(int)> dfs; dfs = [&](int u) { for(int v:son[u]) dfs(v),posr[u]=max(posr[u],posr[v]); };  if(oo) dfs(1);
        For(u,2,tot) posl[u]=posr[u]-len[u]+1;
        For(u,1,tot) tcnt[posl[u]]++;; For(i,1,n) tcnt[i]+=tcnt[i-1];; For(u,1,tot) tmp[tcnt[posl[u]]--]=u;; if(oo) reverse(tmp+1,tmp+1+tot);
        For(i,1,n) tcnt[i]=0;; For(i,1,tot) tcnt[len[tmp[i]]]++;; For(i,1,n) tcnt[i]+=tcnt[i-1];; Rev(i,tot,1) lis[tcnt[len[tmp[i]]]--]=tmp[i];
        Rev(i,tot,1) { int u=lis[i]; for(int v:son[u]) occ[u]+=occ[v]; }
    }
    inline int find(int l,int r) const { int u=pre[r]; Rev(i,__lg(n),0) if(len[ffa[u][i]]>=r-l+1) u=ffa[u][i];; return u; }
};
template<int NN,int Z>
class BasicStringStructure{
public:
    static constexpr int N=NN*2; struct Node { int u,r; }; using pii = pair<int,int>; struct Stair { int posl,posr; vector<Node> X,Y; } st[N];
    int n,stcnt,num0[N],num1[N]; SAM<NN,Z> S0,S1;
    BasicStringStructure<NN,Z>() : S0(0), S1(1) {}
    inline void build(int _n,int* s){
        n=_n; For(i,1,n) S0.push_back(s[i]);; Rev(i,n,1) S1.push_back(s[i]);; fprintf(stderr,"T1:%ld\n",clock()); S0.build(0); S1.build(1);
        stcnt=0; Rev(i,S0.tot,2){
            int u=S0.lis[i],l=S0.posl[u],r=S0.posr[u],l2=l+S0.len[u]-S0.len[S0.fa[u]]-1,v=0; For(c,0,Z-1) if(S0.ch[u][c]&&S0.occ[S0.ch[u][c]]==S0.occ[u]) { v=S0.ch[u][c]; break; }
            if(!v) {  num0[u]=++stcnt; st[stcnt].posl=l; st[stcnt].posr=r; st[stcnt].X.push_back(Node{u,l2}); }
            else { st[num0[u]=num0[v]].X.push_back(Node{u,l2}); }
        }
        stcnt=0; Rev(i,S1.tot,2){
            int u=S1.lis[i],l=n-S1.posr[u]+1,r=n-S1.posl[u]+1,r2=r-(S1.len[u]-S1.len[S1.fa[u]])+1,v=0; For(c,0,Z-1) if(S1.ch[u][c]&&S1.occ[S1.ch[u][c]]==S1.occ[u]) { v=S1.ch[u][c]; break; }
            if(!v) { num1[u]=++stcnt; assume(st[stcnt].posl==l&&st[stcnt].posr==r); st[stcnt].Y.push_back(Node{u,r2}); }
            else { st[num1[u]=num1[v]].Y.push_back(Node{u,r2}); }
        }
        // For(i,1,stcnt){
        //     printf("Stair #%d (%d,%d): X = ",i,st[i].posl,st[i].posr);
        //     for(auto x:st[i].X) printf("%d[%d,%d] ",x.u,x.l,x.r);
        //     printf(" Y = "); for(auto y:st[i].Y) printf("%d[%d,%d] ",y.u,y.l,y.r);; puts("");
        // }
    }
};

本文作者:CharlieVinnie

本文链接:https://www.cnblogs.com/Charlie-Vinnie/p/17093065.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   CharlieVinnie  阅读(686)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起