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

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

后缀数组

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

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后缀自动机上的位置

转载于:https://www.cnblogs.com/dedicatus545/p/10116243.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值