• [学习笔记]后缀相关算法


    SA

    SA实际上求出两个数组\(sa,rk\)

    \(sa_i\)表示将所有后缀排序后排名第\(i\)小的后缀的编号,\(rk_i\)表示后缀\(i\)的排名。

    满足其性质\(sa_{rk_i} = rk_{sa_i} = i\)

    这里仅给出一个\(O(nlog^2n)\)的做法。

    其他做法参见\(oiwiki\)

    点击查看代码
    bool cmp(int i,int j){
    	if(rk[i]!=rk[j]){
    		return rk[i]<rk[j];
    	}else{
    		int ri,rj;
    		ri=i+k<n?rk[i+k]:-1;
    		rj=j+k<n?rk[j+k]:-1;
    		return ri<rj;
    	}
    }
    
    void calc(){
    	for(int i=0;i<n;i++){
    		rk[i]=s[i];
    		sa[i]=i;
    	}
    	for(k=1;k<n;k*=2){
    		sort(sa,sa+n,cmp);
    		tmp[sa[0]]=0;
    		for(int i=0;i<n-1;i++){
    			tmp[sa[i+1]]=tmp[sa[i]]+cmp(sa[i],sa[i+1]);
    		}
    		for(int i=0;i<n;i++){
    			rk[i]=tmp[i];
    		}
    	}
    }
    

    很多情况下我们都要求出辅助数组\(height\)数组。

    \(height_i = lcp(sa_i,sa_{i - 1})\)

    其有引理\(height_{rk_{i - 1}} + 1 \leq height_{rk_{i}}\)

    所以根据该引理,维护一个指针即可\(O(n)\)求出其。

    点击查看代码
    
    for (i = 1, k = 0; i <= n; ++i) {
      if (rk[i] == 0) continue;
      if (k) --k;
      while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
      height[rk[i]] = k;
    }
    

    其应用大概有:

    两个子串最长公共前缀

    \(lcp(sa_i,sa_j) = \min{heaight_{i + 1...j}}\)

    不同子串数目

    \(\frac{n(n+1))}{2} - \sum_{i = 2}^n height_i\)

    连续的相同子串([NOI2016] 优秀的拆分):

    考虑枚举长度然后设置关键点。
    若存在\(AA\),则\(AA\)一定跨越两个关键点,枚举两关键点,其一定有\([1,x][1,y]\)的后缀最长公共子串加\([x,n][y,n]\)的前缀最长公共子串大于枚举长度。
    配合图食用。
    image

    搭配其他数据结构

    略。

    SAM

    SAM是一个接受字符串\(S\)的所有后缀的最小DFA。
    其也是对于将所有子串插入trie里的trie图压缩的结果。

    子串的性质

    \(S\)的子串和从\(t_0\)出发的任意路径对应。

    一些概念和性质

    结束位置 endpos

    考虑字符串的任意非空子串,我们记\(endpos(t)\)为其所有的结束位置。

    考虑按\(endpos(t)\)分成若干等价类。

    显然,\(SAM\)的每个状态对应等价类。

    引理1:字串两个子串为\(u\),\(w\),其\(endpos\)相同,长度小的总为长度长的后缀。

    引理2:每个等价类的元素子串的长度连续,且后缀关系传递。

    后缀链接 \(link\)

    考虑\(SAM\)中的某个不是\(t_0\)的状态\(v\),我们已经知道,状态\(v\)其对应子串均为其代表等价类的最长串(\(T\))的后缀,我们设\(S = suffix(T)\),其等价类\(v\)的后缀链接连向\(\max len(S) (endpos(S)\neq endpos(T))\)对应\(S\)的等价类。

    我们设\(endpos(t_0) = {-1,0,....,|S| - 1}\)

    引理3:所有后缀链接构成一颗根节点为\(t_0\)的树,其上子节点的\(endpos\)是父节点的\(endpos\)的子集。

    后缀自动机建法

    考虑增量建法。

    考虑已经维护了\(S\)的后缀自动机,现在要加入一个字符\(c\),考虑如何维护:

    \(len\)为其对应等价类里的最长长度。

    带图的建树过程

    点击查看代码
    ll nod = 1,lst = 1;
    
    inline void insert(int c){
    	int p = lst,q = ++nod;lst = q;
    	len[q] = len[p] + 1,f[q] = 1;
    	while(!ch[p][c] && p != 0){//向上找 
    		ch[p][c] = q;
    		p = link[p];
    	} 
    	if(p == 0)
    	link[q] = 1;
    	else{
    		int x = ch[p][c];
    		if(len[p] + 1 == len[x]){
    			link[q] = x;
    		}else{
    			int y = ++ nod ;//复制一个新节点
    			link[y] = link[x];
    			link[x] = link[q] = y;
    			len[y] = len[p] + 1;
    			std::memcpy(ch[y],ch[x],sizeof(ch[x])); 
    			while(p != 0 && ch[p][c] == x){
    				ch[p][c] = y;
    				p = link[p];
    			}
    		}
    	}
    }
    
  • 相关阅读:
    BZOJ4008: [HNOI2015]亚瑟王
    BZOJ4260: Codechef REBXOR
    BZOJ4408: [Fj Winter Camp 2016]神秘数
    BZOJ4010: [HNOI2015]菜肴制作
    ccc2016
    BZOJ3884: 上帝与集合的正确用法
    BZOJ1017: [JSOI2008]魔兽地图DotR
    BZOJ1011: [HNOI2008]遥远的行星
    BestCoder Round #73
    hdu4035(概率dp)
  • 原文地址:https://www.cnblogs.com/dixiao/p/15936062.html
Copyright © 2020-2023  润新知