bzoj3238/洛谷4284 [Ahoi2013]差异 (后缀数组/后缀自动机)

bzoj3238/洛谷4284 [Ahoi2013]差异

题意:设 Ti 表示从i开始的后缀。求1ijnlen(Ti)+len(Tj)lcp(Ti,Tj)

方法:显然1ijnlen(Ti)+len(Tj)可以 O(1) 求出,那么问题就在于怎么求1ijnlcs(Ti,Tj)也就是求每两个后缀的公共前缀,解决这个问题可以用后缀数组,也可以用后缀自动机
这里写图片描述
上面的是后缀自动机的..下面是后缀数组
可见…后缀自动机啊..内存真的大…速度真的快,好像还比后缀数组短【捂脸】

方法一:后缀数组
通过后缀数组,我们可以得到height。
题目要求任意两个串的 lcp,我们知道两个串的 lcp,就是他们之间height的最小值
所以就转化成了求序列所有区间最小值*2
然后就可以通过单调栈,找到左边最远做到哪里,右边最远做到哪里,影响就是height[i](il[i]+1)(r[i]i+1)

#include <cstdio>
#include <cstring>
#define N 550000
char str[N];
int n,a[N],r[N],l[N],count[N],rank1[N],rank[2*N],height[N],tmp[N],sa[N],stack[N];
long long ans;
int main() {
    scanf("%s",str+1);n=strlen(str+1);
    memset(count,0,sizeof(count));
    for(int i=1;i<=n;i++) a[i]=str[i]-'a'+1;
    for(int i=1;i<=n;i++) count[a[i]]=1;
    for(int i=1;i<=26;i++) count[i]+=count[i-1];
    for(int i=1;i<=n;i++) rank[i]=count[a[i]];
    int k=0;for(int len=1;k!=n;len<<=1){
        memset(count,0,sizeof(count));
        for(int i=1;i<=n;i++) count[rank[i+len]]++;
        for(int i=1;i<=n;i++) count[i]+=count[i-1];
        for(int i=n;i>=1;i--) tmp[count[rank[i+len]]--]=i;
        memset(count,0,sizeof(count));
        for(int i=1;i<=n;i++) count[rank[tmp[i]]]++;
        for(int i=1;i<=n;i++) count[i]+=count[i-1];
        for(int i=n;i>=1;i--) sa[count[rank[tmp[i]]]--]=tmp[i];
        memcpy(rank1,rank,sizeof(rank1));
        k=1;rank[sa[1]]=1;
        for(int i=2;i<=n;i++){
            if(rank1[sa[i]]!=rank1[sa[i-1]] || rank1[sa[i]+len]!=rank1[sa[i-1]+len]) k++;
            rank[sa[i]]=k;
        }
    }k=0;
    for(int i=1;i<=n;i++){
        if(rank[i]==1) k=0;
        else{
            if(--k<0) k=0;
            while(a[i+k]==a[sa[rank[i]-1]+k]) k++;
        }height[rank[i]]=k; 
    }
    ans=(long long)n*(n+1)*(n-1)/2;
    int top=0;
    for(int i=2;i<=n;i++){
        while(top && height[i]<height[stack[top]]) r[stack[top--]]=i-1;
        stack[++top]=i;
    }while(top) r[stack[top--]]=n;
    top=0;
    for(int i=n;i>=1;i--){
        while(top && height[i]<=height[stack[top]]) l[stack[top--]]=i+1;
        stack[++top]=i;
    }while(top) l[stack[top--]]=2;
    for(int i=2;i<=n;i++) ans-=(long long)2*height[i]*(r[i]-i+1)*(i-l[i]+1);
    printf("%lld",ans);
    return 0;
}

方法二:后缀自动机
我们可以构造出后缀树,然后两个串的最长公共前缀就是他们的lca,而且每个点(c出叶节点)都会有当 lca 的机会。所有我们考虑每个点为 lca 的情况
,必然是在这个点下面选两个点,因此就是 lenCsize[p]2
不过因为每个点都回当lca,所有我们就只做着一段即可(p——fa[p]),剩下的会在fa[p]处处理,不然会有重复

至于后缀树的建立,反串的parent tree才是原串的后缀树

#include <cstdio>
#include <cstring>
#define N 500010
#define ll long long
int cnt=0,last,root,n,mx[N<<1],size[N<<1],son[N<<1][26],fa[N<<1],c[N<<1],a[N<<1],sum[N<<1];
char str[N];
ll ans=0;
inline void ins(int ch){
    int p=last,np=++cnt;last=np;mx[np]=mx[p]+1;size[np]=1;
    while(p && !son[p][ch]) son[p][ch]=np,p=fa[p]; 
    if(!p) fa[np]=root;
    else{
        int q=son[p][ch];
        if(mx[q]==mx[p]+1) fa[np]=q;
        else{
            int nq=++cnt;mx[nq]=mx[p]+1;
            memcpy(son[nq],son[q],sizeof(son[nq]));
            fa[nq]=fa[q];fa[np]=fa[q]=nq;
            while(son[p][ch]==q) son[p][ch]=nq,p=fa[p];
        }
    }
}
int main(){
    scanf("%s",str+1);last=root=++cnt;n=strlen(str+1);
    for(int i=n;i>=1;i--) ins(str[i]-'a');
    for(int i=1;i<=cnt;i++) c[mx[i]]++;
    for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for(int i=cnt;i>=1;i--) a[c[mx[i]]--]=i;
        for(int i=cnt;i>=1;i--){
        int p=a[i];size[fa[p]]+=size[p];
        ans+=(ll)(mx[p]-mx[fa[p]])*size[p]*(size[p]-1);
    }printf("%lld\n",(ll)n*(n+1)*(n-1)/2-ans);
    return 0;
} 
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sunshiness_s/article/details/80690909
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭