NKOJ 4151 (TJOI 2016&HEOI 2016)字符串(后缀数组+倍增+主席树)

【Tjoi2016&Heoi2016】字符串

问题描述

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

输入格式

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

输出格式

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

样例输入

5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4

样例输出

1
1
2
2
2

提示

1<=n,m<=100,000,字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n


此题思路比较直接,考虑到直接搞的麻烦,考虑二分答案,那么问题可以转化成求 [a,bmid+1] 中是否存在一个位置开始的后缀与以 c 为开头的后缀的最长公共前缀大于mid

考虑如何判断,考虑SA数组,显然与c开头的后缀最长公共前缀大于 mid 的后缀在SA数组中是连续的一个区间,那么问题即是求是否存在以 [a,bmid+1] 开头的后缀在SA数组中的位置恰好在上述区间中,那么用主席树维护每个位置对应 SA 数组中出现位置的情况,直接查找即可。

关于找出区间,RMQ处理Height数组,然后倍增查找即可。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define N 200005
#define M 10000005
using namespace std;
char s[N];
int n,m,SA[N],Rank[N],H[N],F[N][20],S=18;
int wa[N],wb[N],T[N];
int tot,ls[M],rs[M],v[M],rt[N];
bool equ(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void GSA(char *r,int *sa,int a,int b)
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<b;i++)T[i]=0;
    for(i=0;i<a;i++)T[x[i]=r[i]]++;
    for(i=1;i<b;i++)T[i]+=T[i-1];
    for(i=a-1;i>=0;i--)sa[--T[x[i]]]=i;
    for(p=1,j=1;p<a;j<<=1,b=p)
    {
        for(p=0,i=a-j;i<a;i++)y[p++]=i;
        for(i=0;i<a;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
        for(i=0;i<b;i++)T[i]=0;
        for(i=0;i<a;i++)T[x[y[i]]]++;
        for(i=1;i<b;i++)T[i]+=T[i-1];
        for(i=a-1;i>=0;i--)sa[--T[x[y[i]]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<a;i++)
        x[sa[i]]=equ(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void GH(char *r,int *sa,int a)
{
    int i,j,k=0;
    for(i=1;i<=a;i++)Rank[sa[i]]=i;
    for(i=0;i<a;H[Rank[i++]]=k)
    for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
}
void RMQ()
{
    int i,j;
    for(i=1;i<=n;i++)F[i][0]=H[i];
    for(i=n;i>=1;i--)
    for(j=1;i+(1<<j-1)<=n;j++)
    F[i][j]=min(F[i][j-1],F[i+(1<<j-1)][j-1]);
}
int CP(int x)
{
    int p=++tot;
    ls[p]=ls[x];
    rs[p]=rs[x];
    v[p]=v[x];
    return p;
}
int ADD(int p,int l,int r,int k)
{
    int o=CP(p);
    if(l==r){v[o]++;return o;}
    int mid=l+r>>1;
    if(k<=mid)ls[o]=ADD(ls[o],l,mid,k);
    else rs[o]=ADD(rs[o],mid+1,r,k);
    v[o]=v[ls[o]]+v[rs[o]];
    return o;
}
int GS(int lp,int rp,int l,int r,int x,int y)
{
    if(x<=l&&y>=r)return v[rp]-v[lp];
    int mid=l+r>>1,cs=0;
    if(x<=mid&&y>=l)cs+=GS(ls[lp],ls[rp],l,mid,x,y);
    if(x<=r&&y>mid)cs+=GS(rs[lp],rs[rp],mid+1,r,x,y);
    return cs;
}
bool ok(int t,int a,int b,int c)
{
    int l=Rank[c],r=Rank[c];
    for(int i=S;i>=0;i--)
    {
        if(l-(1<<i)>=0&&F[l-(1<<i)+1][i]>=t)l=l-(1<<i);
        if(r+(1<<i)<=n&&F[r+1][i]>=t)r=r+(1<<i);
    }
    return GS(rt[a-1],rt[b],1,n,l,r);
}
void EF(int l,int r,int a,int b,int c)
{
    int mid;
    while(l<=r)
    {
        mid=l+r>>1;
        if(ok(mid,a,b-mid+1,c))l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",r);
}
int main()
{
    int a,b,c,d;
    scanf("%d%d%s",&n,&m,s);s[n]='a'-1;
    GSA(s,SA,n+1,200);GH(s,SA,n);RMQ();
    rt[0]=ADD(rt[0],1,n,Rank[0]);
    for(int i=1;i<n;i++)rt[i]=ADD(rt[i-1],1,n,Rank[i]);
    while(m--)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        a--;b--;c--;d--;
        EF(1,min(b-a+1,d-c+1),a,b,c);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值