[BZOJ 3230]相似子串

[BZOJ 3230]相似子串

题意

给定一个长度为 \(n\) 的字符串以及 \(q\) 组查询, 每组查询给定 \(a\)\(b\), 求在所有本质不同子串中排名第 \(a\) 和第 \(b\) 的串的最长公共前缀与最长公共后缀的平方和.

\(n,q\le 1\times 10^5\).

题解

后缀数组板子题.麻麻我终于会用后缀数组辣

本来想接着用SAM的...但是发现多组查询排名为 \(k\) 的本质不同子串以我对SAM的理解好像不能做...于是就用了SA.

最长公共前后缀显然对正反串建两个SA就可以 \(O(n\log n)\rightarrow O(1)\) 算了. 关键在于怎么找到这两个子串.

把所有后缀排序后, 显然每一个后缀对本质不同子串的贡献就是和前一个后缀相同的部分之外的部分了. 那么求出所有 \(height\) 后我们也能知道前 \(k\) 个后缀总共产生了多少本质不同的子串. 直接二分找到子串左端点然后根据差值算出子串长度就可以了.

因为要有两个SA, 所以简单地封到结构体里会比较好写. 然而这根本不算封装

参考代码

#include <bits/stdc++.h>

const int MAXN=1e5+10;
typedef long long intEx;

struct SuffixArray{
    int* x;
    int* y;
    char s[MAXN];
    int SA[MAXN];
    int lg[MAXN];
    int cnt[MAXN];
    int rank[MAXN];
    intEx sum[MAXN];
    int height[MAXN];
    int st[18][MAXN];
    SuffixArray():x(new int[MAXN]),y(new int[MAXN]){}
    void Build();
    int LCP(int,int);
    std::pair<int,int> Query(intEx);
};
SuffixArray a,b;

int n;
int q;

inline intEx Sqr(intEx);

int main(){
    scanf("%d%d",&n,&q);
    scanf("%s",a.s+1);
    for(int i=1;i<=n;i++)
        b.s[i]=a.s[i];
    std::reverse(b.s+1,b.s+n+1);
    a.Build();
    b.Build();
    for(int i=0;i<q;i++){
        intEx x,y;
        scanf("%lld%lld",&x,&y);
        auto p1=a.Query(x);
        auto p2=a.Query(y);
        if(p1.first==-1||p2.first==-1)
            puts("-1");
        else{
            intEx ans=Sqr(std::min(a.LCP(p1.first,p2.first),std::min(p1.second,p2.second)));
            p1.first=n-(p1.first+p1.second-1)+1;
            p2.first=n-(p2.first+p2.second-1)+1;
            ans+=Sqr(std::min(b.LCP(p1.first,p2.first),std::min(p1.second,p2.second)));
            printf("%lld\n",ans);
        }
    }
    return 0;
}

int SuffixArray::LCP(int x,int y){
    if(x==y)
        return INT_MAX;
    else{
        int l=rank[x],r=rank[y];
        if(l>r)
            std::swap(l,r);
        ++l;
        int len=r-l+1;
        return std::min(st[lg[len]][l],st[lg[len]][r-(1<<lg[len])+1]);
    }
}

std::pair<int,int> SuffixArray::Query(intEx k){
    int p=std::lower_bound(sum+1,sum+n+1,k)-sum;
    if(p==n+1)
        return {-1,-1};
    else
        return {SA[p],n-(sum[p]-k)-SA[p]+1};
}

void SuffixArray::Build(){
    int m=127;
    for(int i=1;i<=n;i++)
        ++cnt[x[i]=s[i]];
    for(int i=1;i<=m;i++)
        cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;i--)
        SA[cnt[x[i]]--]=i;
    for(int k=1;k<n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;i++)
            y[++p]=i;
        for(int i=1;i<=n;i++)
            if(SA[i]>k)
                y[++p]=SA[i]-k;
        memset(cnt+1,0,sizeof(int)*m);
        for(int i=1;i<=n;i++)
            ++cnt[x[i]];
        for(int i=1;i<=m;i++)
            cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--)
            SA[cnt[x[y[i]]]--]=y[i];
        std::swap(x,y);
        x[SA[1]]=1;
        p=1;
        for(int i=2;i<=n;i++)
            x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+k]==y[SA[i-1]+k])?p:++p;
        if(p>=n)
            break;
        m=p;
    }
    for(int i=1;i<=n;i++)
        rank[SA[i]]=i;
    int k=0;
    for(int i=1;i<=n;i++){
        if(rank[i]==1)
            continue;
        if(k!=0)
            --k;
        int j=SA[rank[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])
            ++k;
        height[rank[i]]=k;
        st[0][rank[i]]=k;
    }
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+(n-SA[i]+1)-height[i];
    for(int i=1;(1<<i)<=n;i++){
        ++lg[1<<i];
        for(int j=2;j<=n;j++){
            st[i][j]=st[i-1][j];
            if(j+(1<<(i-1))<=n)
                st[i][j]=std::min(st[i][j],st[i-1][j+(1<<(i-1))]);
        }
    }
    for(int i=1;i<=n;i++)
        lg[i]+=lg[i-1];
}

inline intEx Sqr(intEx x){
    return x*x;
}

o_27882019_p0.png

转载于:https://www.cnblogs.com/rvalue/p/10603884.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值