[BZOJ3230]相似子串(后缀数组+二分+ST表)

=== ===

这里放传送门

=== ===

题解

这道题实际上就是给定子串的排名然后求它们的LCP和LCS。所以首先需要对正串和反串分别建立后缀数组,求出height。因为要计算LCP和LCS就要知道它们在正串和反串中所处的位置,所以就考虑如何根据给定的排名求这个东西。在后缀数组里求不相同子串的时候就是按照后缀的排名,每次用这个后缀的前缀数量减去和前一个后缀重复的前缀数量也就是它的height然后再累加到答案里。那也就是说按照字典序考察每个后缀的时候,它贡献的子串也是按照字典序排列的。那么可以考虑二分,把每个后缀贡献的子串求出来存在数组里再做一个前缀和然后在这个数组里二分。这样就可以得到排名为k的子串是由哪一个后缀贡献的,接着就可以得到它在这个后缀里的位置然后就可以得到它原本的长度。在用height数组求LCP和LCS的时候对子串长度取一下Min就没问题了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define Pow 18
#define clear(x)(memset(x,0,sizeof(x)));
using namespace std;
const long long inf=1e18;
int len,q,Xx[100010],Yy[100010],b[100010],m,p,*x,*y,lg[100010];
long long v[100010];
char s[100010];
bool cmp(int i,int j,int l){
    return y[i]==y[j]&&((i+l>=len)?-1:y[i+l])==((j+l>=len)?-1:y[j+l]);
}
struct SuffixArray{
    int SA[100010],rank[100010],height[100010],Min[100010][20];
    void get(){
        clear(b);x=Xx;y=Yy;m=200;
        for (int i=0;i<len;i++) ++b[x[i]=s[i]];
        for (int i=1;i<=m;i++) b[i]+=b[i-1];
        for (int i=len-1;i>=0;i--) SA[--b[x[i]]]=i;
        for (int k=1;k<=len;k<<=1){
            p=0;
            for (int i=len-k;i<len;i++) y[p++]=i;
            for (int i=0;i<len;i++)
              if (SA[i]>=k) y[p++]=SA[i]-k;
            for (int i=0;i<=m;i++) b[i]=0;
            for (int i=0;i<len;i++) ++b[x[i]];
            for (int i=1;i<=m;i++) b[i]+=b[i-1];
            for (int i=len-1;i>=0;i--) SA[--b[x[y[i]]]]=y[i];
            swap(x,y);x[SA[0]]=0;p=1;
            for (int i=1;i<len;i++)
              x[SA[i]]=cmp(SA[i-1],SA[i],k)?p-1:p++;
            if (p>=len) break;m=p;
        }
        p=0;
        for (int i=0;i<len;i++) rank[SA[i]]=i;
        for (int i=0;i<len;i++){
            if (rank[i]==0) continue;
            int j=SA[rank[i]-1];
            while (i+p<len&&j+p<len&&s[i+p]==s[j+p]) ++p;
            height[rank[i]]=p;
            if (p!=0) --p;
        }
        for (int i=0;i<len;i++) Min[i][0]=height[i];
        for (int i=1;i<=Pow;i++)
          for (int j=0;j<len;j++){
              int pos=j+(1<<(i-1));
              if (pos>=len) continue;
              Min[j][i]=min(Min[j][i-1],Min[pos][i-1]);
          }
    }
    int ask(int l,int r,int limit){
        int ans,j;
        l=rank[l];r=rank[r];
        if (l==r) return limit;
        if (l>r) swap(l,r);
        l++;j=lg[r-l+1];
        ans=min(Min[l][j],Min[r-(1<<j)+1][j]);
        ans=min(ans,limit);
        return ans;
    }
}P,N;
void getstr(int len){
    char c;
    for (int i=0;i<len;i++){
        c=getchar();
        while (c<'a'||c>'z') c=getchar();
        s[i]=c;
    }
}
void reverse(){
    for (int i=0;i+i<len;i++)
      swap(s[i],s[len-i-1]);
}
int find(int l,int r,long long x,int &L){
    int mid;
    long long res=x;
    while (l!=r){
        mid=(l+r)>>1;
        if (v[mid]>=x) r=mid;
        else l=mid+1;
    }
    if (l==len) return -1;
    if (l!=0) res-=v[l-1];
    L=res+P.height[l];//计算当前子串的长度
    return P.SA[l];
}
int main()
{
    scanf("%d%d",&len,&q);
    for (int i=1,p=0;i<=len;i++){
        while ((1<<p)<=i) ++p;
        lg[i]=p-1;
    }
    getstr(len);P.get();
    reverse();N.get();
    for (int i=0;i<len;i++) v[i]=len-P.SA[i]-P.height[i];
    for (int i=1;i<len;i++) v[i]+=v[i-1];
    v[len]=inf;//便于判断无解
    for (int i=1;i<=q;i++){
        int xp,yp,xl,yl,limit;
        long long x,y,LCP,LCS;
        scanf("%I64d%I64d",&x,&y);
        xp=find(0,len,x,xl);
        yp=find(0,len,y,yl);
        if (xp==-1||yp==-1){
            printf("-1\n");continue;
        }
        limit=min(xl,yl);//注意LCP和LCS的最长长度不能超过子串长度
        LCP=P.ask(xp,yp,limit);
        xp=xp+xl-1;yp=yp+yl-1;
        xp=len-xp-1;yp=len-yp-1;//求出当前这个串在反串中的位置
        LCS=N.ask(xp,yp,limit);
        LCP*=LCP;LCS*=LCS;
        printf("%I64d\n",LCP+LCS);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值