洛谷 P4094 [HEOI2016/TJOI2016]字符串 后缀数组+二分+主席树

本文介绍了一种解决特殊字符串查询问题的方法,即寻找两个子串间最长公共前缀的最大值。通过构建后缀数组并结合二分搜索与线段树,实现了高效的查询算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

输入输出格式

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

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

输入输出样例

输入样例#1:
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
1
2
2
2
说明

对于10%的数据,1&lt;=n,m&lt;=3001&lt;=n,m&lt;=3001<=n,m<=300

对于40%的数据,1&lt;=n,m&lt;=30001&lt;=n,m&lt;=30001<=n,m<=3000,字符串中仅有a,ba,ba,b

对于100%的数据,1&lt;=n,m&lt;=1000001&lt;=n,m&lt;=1000001<=n,m<=100000,字符串中仅有小写英文字母,a&lt;=b,c&lt;=d,1&lt;=a,b,c,d&lt;=na&lt;=b,c&lt;=d,1&lt;=a,b,c,d&lt;=na<=b,c<=d,1<=a,b,c,d<=n\

分析:
S=s[a..b]S=s[a..b]S=s[a..b],显然只有SSS的后缀才对答案有贡献。我们先对sss建一个后缀数组,相当于求与以ccc开头的后缀的与SSS的后缀的lcplcplcp最大值(大概可以这样理解,虽然有边界问题)。
考虑二分一个答案,显然这个答案的上限是min(b−a+1,d−c+1)min(b-a+1,d-c+1)min(ba+1,dc+1),对于一个答案midmidmid,可以二分出与ccc这个后缀的lcplcplcp大于等于midmidmid的区间。显然后缀排序后,一个后缀与其他后缀的lcplcplcp是一个单峰函数。
加入二分出来的区间为[lc,rc][lc,rc][lc,rc],而SSS串中只有前[a,b−mid+1][a,b-mid+1][a,bmid+1]个后缀长度超过midmidmid,相当于查询在排名为lclclcrcrcrc的后缀中,有没有一个后缀是原串的第[a,b−mid+1][a,b-mid+1][a,bmid+1]位。我们可以以排名为下标建可持久化权值线段
树即可。
要先预处理logloglog,因为c++自带的求logloglog采用的是二分,复杂度是O(logn)O(logn)O(logn)的。
所以总复杂度是O(nlog2n)O(nlog^2n)O(nlog2n)级别的。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>

const int maxn=1e5+7;

using namespace std;

int n,m,cnt;
int x[maxn],y[maxn],c[maxn],root[maxn],Log2[maxn];
char s[maxn];

struct node{
    int l,r,data;
}t[maxn*31];

struct suffix_array{
    int h[maxn][20],rank[maxn],sa[maxn];
    void getsa()
    {
        int m=1000;
        for (int i=1;i<=m;i++) c[i]=0;
        for (int i=1;i<=n;i++) x[i]=s[i];
        for (int i=1;i<=n;i++) c[x[i]]++;
        for (int i=1;i<=m;i++) c[i]+=c[i-1];
        for (int i=n;i>0;i--) sa[c[x[i]]--]=i;
        for (int k=1;k<=n;k<<=1)
        {
            int num=0;
            for (int i=n-k+1;i<=n;i++) y[++num]=i;
            for (int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
            for (int i=1;i<=m;i++) c[i]=0;
            for (int i=1;i<=n;i++) c[x[i]]++;
            for (int i=1;i<=m;i++) c[i]+=c[i-1];
            for (int i=n;i>0;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            num=1;
            x[sa[1]]=1;
            for (int i=2;i<=n;i++)
            {
                if ((y[sa[i]]!=y[sa[i-1]]) || (y[sa[i]+k]!=y[sa[i-1]+k]))
                {
                    x[sa[i]]=++num;
                }
                else x[sa[i]]=num;
            }
            if (num>=n) break;
            m=num;
        }
        for (int i=1;i<=n;i++) rank[i]=x[i];
    }
    void getheight()
    {
    	int k=0;
    	for (int i=1;i<=n;i++)
        {
            if (k) k--;
            int j=sa[rank[i]-1];
            while ((i+k<=n) && (j+k<=n) && (s[i+k]==s[j+k])) k++;
            h[rank[i]][0]=k;
        }
    	int c=1;
        for (int j=1;j<20;j++)
        {
            for (int i=1;i<=n;i++)
            {
            	if (i+c>n) h[i][j]=h[i][j-1];
                      else h[i][j]=min(h[i][j-1],h[i+c][j-1]);
            }
            c<<=1;
        }
    }
    int lcp(int x,int y)
    {
        x=rank[x],y=rank[y];
        if (x>y) swap(x,y);
        x++;
        int k=Log2[y-x+1];
        return min(h[x][k],h[y-(1<<k)+1][k]);
    }
}a;

void ins(int &p,int q,int l,int r,int x)
{
    if (!p) p=++cnt;
    t[p].data=t[q].data+1;
    if (l==r) return;
    int mid=(l+r)/2;
    if (x<=mid) t[p].r=t[q].r,ins(t[p].l,t[q].l,l,mid,x);
           else t[p].l=t[q].l,ins(t[p].r,t[q].r,mid+1,r,x);
}

int query(int p,int q,int l,int r,int x,int y)
{
    if ((l==x) && (r==y)) return t[p].data-t[q].data;
    if (t[p].data-t[q].data==0) return 0;
    int mid=(l+r)/2;
    if (y<=mid) return query(t[p].l,t[q].l,l,mid,x,y);
    else if (x>mid) return query(t[p].r,t[q].r,mid+1,r,x,y);
    else return query(t[p].l,t[q].l,l,mid,x,mid)+query(t[p].r,t[q].r,mid+1,r,mid+1,y);
}

int find1(int d,int x)
{
    int l=1,r=x-1,ans=x;
    while (l<=r)
    {
        int mid=(l+r)/2;
        if (a.lcp(a.sa[mid],a.sa[x])>=d) r=mid-1,ans=mid;
                                    else l=mid+1;
    }
    return ans;
}

int find2(int d,int x)
{
    int l=x+1,r=n,ans=x;
    while (l<=r)
    {
        int mid=(l+r)/2;
        if (a.lcp(a.sa[x],a.sa[mid])>=d) l=mid+1,ans=mid;
                                    else r=mid-1;
    }
    return ans;
}


int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    a.getsa();		
    a.getheight();			
    for (int i=1;i<=n;i++)
    {
        ins(root[i],root[i-1],1,n,a.sa[i]);
    }		
    for (int i=1;i<=n;i++) Log2[i]=trunc(log(i+0.5)/log(2));
    for (int i=1;i<=m;i++)
    {
        int x,y,A,B;
        scanf("%d%d%d%d",&x,&y,&A,&B);
        int l=1,r=min(B-A+1,y-x+1),ans=0;
        while (l<=r)
        {
            int mid=(l+r)/2;
            int lc=find1(mid,a.rank[A]),rc=find2(mid,a.rank[A]);
            if (query(root[rc],root[lc-1],1,n,x,y-mid+1)) ans=mid,l=mid+1;
                                                     else r=mid-1;
        }
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值