• 省选算法学习-后缀数组+后缀自动机+后缀树


    其实很久以前就学了这两个东西......但是一直懒得写,今天补一补

    后缀数组

    基础部分不讲了,放个板子在这

    void bsort(){
        int i;
        for(i=0;i<=m;i++) book[i]=0;
        for(i=1;i<=n;i++) book[rank[i]]++;
        for(i=1;i<=m;i++) book[i]+=book[i-1];
        for(i=n;i>=1;i--) sa[book[rank[tmp[i]]]--]=tmp[i];
    }
    void getsa(){
        int k,cnt,i,j;m=127;
        for(i=1;i<=n;i++) rank[i]=a[i],tmp[i]=i;
        bsort();
        for(k=1,cnt=1;cnt<n;m=cnt,k<<=1){
            cnt=0;
            for(i=1;i<=k;i++) tmp[++cnt]=n-k+i;
            for(i=1;i<=n;i++) if(sa[i]>k) tmp[++cnt]=sa[i]-k;
            bsort();
            swap(rank,tmp);
            rank[sa[1]]=cnt=1;
            for(i=2;i<=n;i++)
                rank[sa[i]]=(tmp[sa[i]]==tmp[sa[i-1]]&&tmp[sa[i]+k]==tmp[sa[i-1]+k])?cnt:++cnt;
        }
        k=0;
        for(i=1;i<=n;height[rank[i++]]=k)
            for((k?k--:k),j=sa[rank[i]-1];a[i+k]==a[j+k];k++);
    }
    

    应用嘛,还是很广泛的

    本体可以后缀排序【废话】

    求出height并且st表一下,就可以求内部lcp啦之类的,还可以套主席树使用

    多串的时候可以考虑首尾相连中间加分隔符'$'然后建SA,操作也很多

    听说还能套FFT一起用?我反正没见过

    例题

    套主席树的

    套可持久化线段树的:十二省联考2019 字符串(虽然实际上是SAM题)

    别的懒得放了......很多操作现想就好

    后缀自动机

    Candy?菊苣讲的非常好,放在这里:Candy?

    理解稍微有点困难......但是把有些概念当显然成立的背下来会有很大帮助

    每个节点上$right$集合是核心维护的东西,维护的是这个节点可以识别的子串的可能起点

    对于每个节点$s$,都有一个范围$[min(s),max(s)]$

    从$right$集合中的位置出发,长度在这个范围内的子串,被SAM识别以后就放在这个点!

    SAM也有fail树,其实更常见叫$parent$ $tree$,相当于失配指针

    对于节点$fa$和节点$s$,有性质:$max(fa)=min(s)-1$

    同时,$right(fa)$是所有包含$right(s)$的集合中,最小的一个

    两个基本用法:

    往对应的儿子走,就是匹配串后面加个字符

    往自己的fail走,就是匹配串前面删个字符

    板子在这里

    
    namespace sam{
    	int ch[2000010][26],fa[2000010],val[2000010],siz[2000010],lis[2000010],book[2000010],root,cnt,last;
    	void init(){root=cnt=last=1;val[1]=0;}
    	inline int newnode(int w){val[++cnt]=w;return cnt;}
    	void insert(int c){
    		int p=last,np=newnode(val[p]+1);siz[np]++;
    		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
    		if(!p) fa[np]=root;
    		else{
    			int q=ch[p][c];
    			if(val[q]==val[p]+1) fa[np]=q;
    			else{
    				int nq=newnode(val[p]+1);
    				memcpy(ch[nq],ch[q],sizeof(ch[q]));
    				fa[nq]=fa[q];
    				fa[np]=fa[q]=nq;
    				for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
    			}
    		}
    		last=np;
    	}
    	void sort(int n){
    		int i;
    		for(i=1;i<=cnt;i++) book[val[i]]++;
    		for(i=1;i<=n;i++) book[i]+=book[i-1];
    		for(i=cnt;i>=1;i--) lis[book[val[i]]--]=i;
    		for(i=cnt;i>=1;i--){
    			siz[fa[lis[i]]]+=siz[lis[i]];
    		}
    	}
    }
    

    广义后缀自动机

    广义的东西,就是把很多串建成$trie$然后$bfs$插入,要记录$last$指针下来

    维护技巧

    $right$集合里很多信息都可以维护,不止大小,还可以状压维护属于哪个串之类的

    然后,有个操作是不做最后面那个基数排序,而是每次插入完从$np$往上更新$siz$,遇到更新过的就$break$

    这种时间复杂度上界好像是$O(nlogn)$,数据小(1e5)的可以用用,不要老是用

    $fail$树可以套到$lct$上,然后可以动态加元素、动态$dp$之类的

    建立反串的SAM,其parent树就是原串的后缀树!

    对于一个子串$[l,r]$,可以用在后缀树上倍增的方式快速定位。

    具体而言,我们每一次完成插入的时候记录$last$指向的节点:那个节点就是对应的后缀在后缀树上的位置

    倍增的时候,从$l$对应的节点开始倍增,找到最浅的,$right$集合适应区间包含$r-l+1$的节点,就是这个子串在后缀树or后缀自动机上的位置

  • 相关阅读:
    fatal error C1083: 无法打开包括文件:“iostream.h”: No such file or directory
    Dan Saks
    '=' : left operand must be lvalue 左值和右值
    sizeof使用
    stream.js :一个新的JavaScript数据结构
    Kibo:键盘事件捕捉高手
    c中不能用引用的办法
    分布式版本控制工具:git与Mercurial
    非常好的BASH脚本编写教程
    Handler让主线程和子线程进行通信
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/10116243.html
Copyright © 2020-2023  润新知