【JZOJ 4614】字符串

Description

这里写图片描述

Solution

先用SA,把rank和sa数组弄出来,
现在二分一个mid表示答案,再二分找出在最左和最右边界L,R,使L和R为在rank中,与后缀c的最长公共前缀>=mid,再用主席树找出在这个rank区间内,是否有后缀在原来的序列中在a~(b-mid+1)之间,也就是是否有区间是合法的,
第二个二分可以用RMQ来找最长公共前缀,
复杂度: O(nlog2(n))

此题要卡常!
SA用 O(nlog(n)) 的,
把RMQ的log先预处理减小常数,
主席树也要注意,不要写得常数巨大,

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=100500;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,ans,S;
char a[N];
int er[25];
int rk[N],h[N],sa[N];
int tp[N][3],ptp[N][3];
int tpt[N];
int rmq[N][25];
struct qqww{int l,r,s;}b[N*25];
int root[N],b0;
int lg[N];
int min(int q,int w){return q<w?q:w;}
int max(int q,int w){return q>w?q:w;}
void TP()
{
    fill(tpt,tpt+n+2,0);
    fo(i,1,n)tpt[tp[i][2]]++;
    fo(i,1,max(n,130))tpt[i]+=tpt[i-1];
    fo(i,1,n)
    {
        int q=tpt[tp[i][2]]--;
        fo(j,0,2)ptp[q][j]=tp[i][j];
    }
    fill(tpt,tpt+max(n,130)+2,0);
    fo(i,1,n)tpt[ptp[i][1]]++;
    fo(i,2,max(n,130))tpt[i]+=tpt[i-1];
    fod(i,n,1)
    {
        int q=tpt[ptp[i][1]]--;
        fo(j,0,2)tp[q][j]=ptp[i][j];
    }   
}
void SA()
{
    er[0]=1;fo(i,1,20)er[i]=er[i-1]<<1;
    fo(i,1,n)rk[i]=a[i];
    int q,w,I=0;
    while(er[I]<=n)
    {
        fo(i,1,n)tp[i][0]=i,tp[i][1]=rk[i],tp[i][2]=i+er[I]<=n?rk[i+er[I]]:0;
        TP();
        q=1;
        fo(i,1,n)rk[tp[i][0]]=q,q+=(tp[i][1]!=tp[i+1][1]||tp[i][2]!=tp[i+1][2]);
        I++;
    }
    fo(i,1,n)sa[rk[i]]=i;
    fo(i,1,n)
    {
        h[i]=max(h[i-1]-1,0);
        q=sa[rk[i]-1];
        if(q)while(q+h[i]<=n&&a[i+h[i]]==a[q+h[i]])h[i]++;
    }
    fo(i,1,n)rmq[rk[i]][0]=h[i];
    fo(j,1,20)fo(i,1,n)rmq[i][j]=min(rmq[i][j-1],((i+er[j-1]<=n)?rmq[i+er[j-1]][j-1]:0));
}
int RMQ(int l,int r)
{
    if(l==r)return n-sa[l]+1;
    int t=lg[r-l];
    return min(rmq[l+1][t],rmq[r-er[t]+1][t]);
}
void build(int l,int r,int e1,int &e2,int l1)
{
    e2=++b0;
    if(l==r){b[e2].s=b[e1].s+1;return;}
    b[e2]=b[e1];
    int mid=(l+r)>>1;
    if(l1<=mid)build(l,mid,b[e1].l,b[e2].l,l1);
        else build(mid+1,r,b[e1].r,b[e2].r,l1);
    b[e2].s=b[b[e2].l].s+b[b[e2].r].s;
}
int EF(int l,int r,int t)
{
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(RMQ(mid,S)<t)l=mid+1;else r=mid;
    }
    return l;
}
int EF1(int l,int r,int t)
{
    while(l<r)
    {
        int mid=((l+r)>>1)+1;
        if(RMQ(S,mid)<t)r=mid-1;else l=mid;
    }
    return l;
}
int find(int l,int r,int e1,int e2,int l1,int r1)
{
    if(!e2||0==b[e2].s-b[e1].s)return 0;
    if(l==l1&&r==r1)return b[e2].s-b[e1].s;
    int t=(l+r)>>1;
    if(r1<=t)return find(l,t,b[e1].l,b[e2].l,l1,r1);
        if(t<l1)return find(t+1,r,b[e1].r,b[e2].r,l1,r1);
            else return find(l,t,b[e1].l,b[e2].l,l1,t)+find(t+1,r,b[e1].r,b[e2].r,t+1,r1);
}
int main()
{
    int q,w,m_,q1,w1;
    read(n);read(m_);
    char ch=' ';
    while(ch<'a'||ch>'z')ch=getchar();
    fo(i,1,n)a[i]=ch,ch=getchar();
    SA();
    root[0]=0;
    fo(i,1,n)build(1,n,root[i-1],root[i],sa[i]);
    fo(i,1,n)lg[i]=log2(i);
    while(m_--)
    {
        read(q),read(w),read(q1),read(w1);
        S=rk[q1];
        int l=0,r=min(w1-q1,w-q)+1,t,t1;
        while(l<r)
        {
            int mid=((l+r)>>1)+1;
            t=EF(1,rk[q1],mid);
            t1=EF1(rk[q1],n,mid);
            if(find(1,n,root[t-1],root[t1],q,w-mid+1))l=mid;
                else r=mid-1;
        }
        ans=l;
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值