【BZOJ5304】[HAOI2018]字串覆盖(后缀数组,主席树,倍增)

本文详细解析BZOJ5304字串覆盖问题,采用后缀数组、主席树和倍增算法处理字符串匹配,针对不同模板串长度,提出高效解法,包括贪心策略、数据预处理及树状结构遍历。

【BZOJ5304】[HAOI2018]字串覆盖(后缀数组,主席树,倍增)

题面

BZOJ
洛谷

题解

贪心的想法是从左往右,能选就选。这个显然是正确的。
题目的数据范围很好的说明了要对于询问分开进行处理。
先考虑询问的模板串长比较大的情况。
那么只需要每次找到一个范围内的最小位置然后接着暴力跳就可以了。
这个这个过程可以把\(AB\)两个串拼接在一起求\(SA\),这样能够匹配上\(P\)串的\(A\)的后缀的起始位置在\(SA\)上就是一段连续区间。考虑每次找出在\(A\)\([l,r]\)范围内的合法的最小值,那么对于后缀数组的每个位置对应的\(A\)上的位置构建主席树,这样子每次就可以在主席树上进行二分来寻找到第一个合法的位置。这样一来反复横跳就可以得到匹配的答案。
这样子的时间复杂度是\(O(n/|P|*logn)\),当给定串长比较大的时候是可以接受的。
但是当\(|P|\)很小的时候显然不能暴跳,因此肯定需要预处理答案。
对于每个\(len\)进行预处理,显然每个合法的串的起始位置都可以找到唯一的一个最小的合法位置(或者不存在),满足以这两个位置为起始位置往后匹配\(len\)位相等,并且两个长度为\(len\)的串不交。
显然这样子的结构构成了一棵树,那么把树给构出来之后,对于小于特定值的询问在树上倍增计算即可。
根据题目数据范围显然两种方法的分界线是\(50\)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 200100
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,K,m,lg[MAX];
char A[MAX];
struct SuffixArray
{
    int hg[20][MAX],SA[MAX],rk[MAX];
    int a[MAX],t[MAX],x[MAX],y[MAX];
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void GetSA()
    {
        int m=255;
        for(int i=1;i<=n;++i)t[x[i]=a[i]]+=1;
        for(int i=1;i<=m;++i)t[i]+=t[i-1];
        for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1)
        {
            int p=0;
            for(int i=n-k+1;i<=n;++i)y[++p]=i;
            for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
            for(int i=1;i<=m;++i)t[i]=0;
            for(int i=1;i<=n;++i)t[x[y[i]]]+=1;
            for(int i=1;i<=m;++i)t[i]+=t[i-1];
            for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
            swap(x,y);x[SA[1]]=p=1;
            for(int i=2;i<=n;++i)x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
            if(p>=n)break;m=p;
        }
        for(int i=1;i<=n;++i)rk[SA[i]]=i;
        for(int i=1,j=0;i<=n;++i)
        {
            if(j)--j;
            while(a[i+j]==a[SA[rk[i]+1]+j])++j;
            hg[0][rk[i]]=j;
        }
        for(int j=1;j<=lg[n];++j)
            for(int i=1;i+(1<<j)-1<=n;++i)
                hg[j][i]=min(hg[j-1][i],hg[j-1][i+(1<<(j-1))]);
    }
}SA;
struct SegmentTree
{
    struct Node{int ls,rs,v;}t[MAX*20];
    int tot;
    void Modify(int &x,int l,int r,int p)
    {
        t[++tot]=t[x];x=tot;t[x].v+=1;
        if(l==r)return;int mid=(l+r)>>1;
        if(p<=mid)Modify(t[x].ls,l,mid,p);
        else Modify(t[x].rs,mid+1,r,p);
    }
    int Query(int x,int y,int l,int r,int p)
    {
        if(!(t[y].v-t[x].v))return 0;
        if(l==r)return l;
        int mid=(l+r)>>1,ret=0;
        if(p<=mid)ret=Query(t[x].ls,t[y].ls,l,mid,p);
        if(!ret)ret=Query(t[x].rs,t[y].rs,mid+1,r,p);
        return ret;
    }
}T;
int rt[MAX];ll ans[MAX];
struct Qry{int s,t,l,len,pos,id;}Q[MAX];
bool operator<(Qry a,Qry b){return a.len!=b.len?a.len<b.len:a.pos<b.pos;}
int fa[20][MAX];ll sum[20][MAX];
void Work(int len,int &L)
{
    static int pos[MAX];
    for(int l=1,r=l;l<=n+n+1;r=l=r+1)
    {
        if(Q[L].len!=len)return;
        int cnt=0;
        while(SA.hg[0][r]>=len)++r;
        for(int j=l;j<=r;++j)if(SA.SA[j]<=n)pos[++cnt]=SA.SA[j];
        if(Q[L].pos<l||Q[L].pos>r)continue;
        sort(&pos[1],&pos[cnt+1]);pos[cnt+1]=n+n+1;
        int k=1;
        for(int i=1;i<=cnt;++i)
        {
            while(pos[k]-pos[i]<len)++k;
            fa[0][i]=k;sum[0][i]=K-pos[i];
        }
        int lim=lg[min(cnt+1,n/len)];
        for(int j=1;j<=lim;++j)
            for(int i=1;i<=cnt;++i)
                fa[j][i]=fa[j-1][fa[j-1][i]],sum[j][i]=sum[j-1][i]+sum[j-1][fa[j-1][i]];
        while(Q[L].len==len&&l<=Q[L].pos&&Q[L].pos<=r)
        {
            int s=Q[L].s,t=Q[L].t,x=lower_bound(&pos[1],&pos[cnt+2],s)-pos;
            if(pos[x]<=t)
            {
                int u=x;ll ret=0;
                for(int i=lim;~i;--i)
                    if(pos[fa[i][u]]&&pos[fa[i][u]]<=t)
                        ret+=sum[i][u],u=fa[i][u];
                ans[Q[L].id]=ret+sum[0][u];
            }
            ++L;
        }
        for(int i=0;i<=lim;++i)
            for(int j=1;j<=cnt+1;++j)
                fa[i][j]=sum[i][j]=0;
    }
}
int main()
{
    n=read();K=read();
    scanf("%s",A+1);A[n+1]='#';scanf("%s",A+n+2);
    n=n+n+1;for(int i=1;i<=n;++i)SA.a[i]=A[i];
    for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
    SA.GetSA();n>>=1;
    m=read();
    for(int i=1;i<=m;++i)
    {
        int s=read(),t=read(),l=read(),r=read();
        Q[i]=(Qry){s,t-(r-l),l,r-l+1,SA.rk[n+1+l],i};
    }
    sort(&Q[1],&Q[m+1]);
    int pos=1;
    for(int i=1;i<=n&&i<=50;++i)Work(i,pos);
    for(int i=1;i<=n+n+1;++i)
        if(SA.SA[i]<=n)T.Modify(rt[i]=rt[i-1],1,n,SA.SA[i]);
        else rt[i]=rt[i-1];
    for(;pos<=m;++pos)
    {
        int s=Q[pos].s,t=Q[pos].t,len=Q[pos].len;
        int x=Q[pos].pos,l=x,r=x;
        for(int i=lg[n]+1;~i;--i)
            if(l-(1<<i)>0&&SA.hg[i][l-(1<<i)]>=len)l-=1<<i;
        for(int i=lg[n]+1;~i;--i)
            if(r+(1<<i)<=n+n+1&&SA.hg[i][r]>=len)r+=1<<i;
        for(int u=s,v;u<=n;u=v+len)
        {
            v=T.Query(rt[l-1],rt[r],1,n,u);
            if(v>t||!v)break;
            ans[Q[pos].id]+=K-v;
        }
    }
    for(int i=1;i<=m;++i)printf("%lld\n",ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10407010.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值