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

bzoj3238/洛谷4284 [Ahoi2013]差异

题意:设 Ti T i 表示从i开始的后缀。求 1ijnlen(Ti)+len(Tj)lcp(Ti,Tj) ∑ 1 ≤ i ≤ j ≤ n l e n ( T i ) + l e n ( T j ) − l c p ( T i , T j )

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

方法一:后缀数组
通过后缀数组,我们可以得到height。
题目要求任意两个串的 lcp,我们知道两个串的 lcp,就是他们之间height的最小值
所以就转化成了求序列所有区间最小值*2
然后就可以通过单调栈,找到左边最远做到哪里,右边最远做到哪里,影响就是 height[i](il[i]+1)(r[i]i+1) h e i g h t [ i ] ∗ ( i − l [ 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 l c a ,而且每个点(c出叶节点)都会有当 lca l c a 的机会。所有我们考虑每个点为 lca l c a 的情况
,必然是在这个点下面选两个点,因此就是 lenC2size[p] l e n ⋅ C s i z e [ 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;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值