[bzoj4556][TJOI&&HEOI2016]字符串

4556: [Tjoi2016&Heoi2016]字符串

Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 179 Solved: 94
[Submit][Status][Discuss]
Description

佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE
O,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公
共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?
Input

输入的第一行有两个正整数n,m,分别表示字符串的长度和询问的个数。接下来一行是一个长为n的字符串。接下来
m行,每行有4个数a,b,c,d,表示询问s[a..b]的所有子串和s[c..d]的最长公共前缀的最大值。1<=n,m<=100,000,
字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n
Output

对于每一次询问,输出答案。

Sample Input

5 5

aaaaa

1 1 1 5

1 5 1 1

2 3 2 3

2 4 2 3

2 3 2 4
Sample Output

1

1

2

2

2

开始想了一种 O(logn) (但是有些问题),大致是这样的。
可以发现在求 lcp 的时候用的是区间最小值,这样假如当求一个位置的最大 lcp 的时候,那肯定是找这个位置 rank 的前驱和后继是最优的,因为再往前找的话答案是不增的。
想到这点之后就直接写了个主席树。
主席树中可以存区间的元素个数和,查询的时候先查询一下区间和,再在区间中像平衡树那样看看需要往哪边走,就能 logn 的找到前驱后继了。
但是这样直接找前驱后继会有个问题,比如这组数据:

5 1
aaaab
1 2 3 5

这组数据在找后继的时候找的是 aaab 这个后缀,但是由于这个有区间长度的限制,所以最后找到的答案是 1 。但是更优的方案是找aaaab这个后缀,这样就可以得到 2 这个答案。
为了解决这个问题,就需要在外面套个二分答案,这个二分就相当于在选了aaab这个后缀之后,可以继续往后选。也就相当于是二分的可选择区间的右端点。
时间复杂度是 O(mlog2n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
const int M=4000000;
int n,m,o,s[N],t1[N],t2[N],c[N],sa[N],rank[N],height[N],st[N][20],Log[N],root[N],l[M],r[M],sum[M],siz;
inline int in(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline bool cmp(int *y,int p,int q,int k){
    int o0=(p+k>=n)?-1:y[p+k];
    int o1=(q+k>=n)?-1:y[q+k];
    return o0==o1&&y[p]==y[q];
}
inline void build_sa(){
    int i,k,p,*x=t1,*y=t2;
    for(o=27,i=0;i<o;++i) c[i]=0;
    for(i=0;i<n;++i) ++c[x[i]=s[i]];
    for(i=1;i<o;++i) c[i]+=c[i-1];
    for(i=n-1;~i;--i) sa[--c[x[i]]]=i;
    for(k=1;k<=n;k<<=1){
        for(p=0,i=n-k;i<n;++i) y[p++]=i;
        for(i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(i=0;i<o;++i) c[i]=0;
        for(i=0;i<n;++i) ++c[x[y[i]]];
        for(i=1;i<o;++i) c[i]+=c[i-1];
        for(i=n-1;~i;--i) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        x[sa[0]]=0;o=1;
        for(i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k)?o-1:o++;
        if(o>=n) break;
    }
}
inline void build_height(){
    int i,k=0,j;
    for(i=0;i<n;++i) rank[sa[i]]=i;
    for(i=0;i<n;++i){
        if(!rank[i]) continue;
        k=k?--k:k;
        j=sa[rank[i]-1];
        while(s[j+k]==s[i+k]) ++k;
        height[rank[i]]=k;
    }
    memset(st,127/3,sizeof(st));    
    for(i=0;i<n;++i) st[i][0]=height[i];
    for(j=1;j<=20;++j)
      for(i=0;i+(1<<(j-1))<n;++i)
        st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    for(j=0,i=1;i<=n;++i){
        if((1<<(j+1))<=i) ++j;
        Log[i]=j;
    }
}
inline int LCP(int x,int y){
    if(x>y) swap(x,y);
    int k=Log[y-x];++x;
    return min(st[x][k],st[y-(1<<k)+1][k]);   
}
#define mid (L+R)/2
inline void insert(int L,int R,int x,int &y,int z){
    y=++siz;
    sum[y]=sum[x]+1;
    if(L>=R) return ;
    l[y]=l[x];r[y]=r[x];
    if(z<=mid) insert(L,mid,l[x],l[y],z);
    else insert(mid+1,R,r[x],r[y],z);
}
inline int query_sum(int L,int R,int x,int y,int left,int right){
    int now=0;
    if(y<x) return 0;
    if(left<=L&&right>=R) return sum[y]-sum[x];
    if(left<=mid) now+=query_sum(L,mid,l[x],l[y],left,right);
    if(right>mid) now+=query_sum(mid+1,R,r[x],r[y],left,right);
    return now;
}
inline int query_pre(int L,int R,int x,int y,int left,int right,int now){
    if(L==R) return L;
    if(sum[l[y]]-sum[l[x]]>=now) return query_pre(L,mid,l[x],l[y],left,right,now);
    else return query_pre(mid+1,R,r[x],r[y],left,right,now-(sum[l[y]]-sum[l[x]]));
}
inline int query_sub(int L,int R,int x,int y,int left,int right,int now){
    if(L==R) return L;
    if(sum[r[y]]-sum[r[x]]>=now) return query_sub(mid+1,R,r[x],r[y],left,right,now);
    else return query_sub(L,mid,l[x],l[y],left,right,now-(sum[r[y]]-sum[r[x]]));
}
int main(){
    int i,now,left,right;
    scanf("%d%d",&n,&m);
    for(i=0;i<n;++i){
        char ch=getchar();
        while(ch<'a'||ch>'z') ch=getchar();
        s[i]=ch-'a'+1;
    }
    build_sa();
    build_height();
    for(i=0;i<n;++i) insert(0,n,root[i],root[i+1],rank[i]);
    while(m--){
        int aa,bb,cc,dd;
        aa=in();bb=in();cc=in();dd=in();
        int L=0,R=bb-aa+1,maxn=0;
        while(L<R){
            now=query_sum(0,n,root[aa-1],root[bb-mid],0,rank[cc-1]-1);
            left=now?query_pre(0,n,root[aa-1],root[bb-mid],0,rank[cc-1],now):-1;
            now=query_sum(0,n,root[aa-1],root[bb-mid],rank[cc-1]+1,n);
            right=now?query_sub(0,n,root[aa-1],root[bb-mid],rank[cc-1],n,now):-1;
            int ans=0;
            if(left!=-1){
                int oo=min(LCP(left,rank[cc-1]),bb-sa[left]);
                ans=max(ans,min(dd-cc+1,oo));
            }
            if(right!=-1){
                int oo=min(LCP(rank[cc-1],right),bb-sa[right]);
                ans=max(ans,min(dd-cc+1,oo));
            }
            if(cc>=aa&&cc<=bb) ans=max(ans,min(dd,bb)-cc+1);
            maxn=max(maxn,ans);
            if(ans>=mid) L=mid+1;
            else R=mid;
        }
        printf("%d\n",maxn);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值