Title

P7409 SvT 题解

前置知识

  • 后缀数组;
  • height 数组。

题目大意

给出一个字符串 s,每次查询 s 若干个后缀两两之间的 LCP 和,答案对 23333333333333333 取模。

解题思路

首先对于 s 求出 height 数组和 rank 数组,接下来我们可以对所有后缀在 s 的后缀排序后的位置,即 ranki 进行从小到大排序。因为可能存在相同的后缀,所以这里需要离散化一下。那么,这个时候问题即转化为求这些排序后的后缀两两之间的 LCP。

考虑 height 数组的性质:

  • 对于排序后的两个后缀 suffixisuffixj,有 LCP(suffixi,suffixj)=mink=ijheightk

那么,设每次查询的后缀,排序后起始位置为 suf1,,suft,那么相邻两个后缀的 LCP 为 minj=sufisufi+1heighti,这个时候我们就可以用 ST 表来快速计算相邻两个后缀的 LCP。

但是,显然我们不能枚举两个后缀然后计算它们的 LCP。这个时候我们就需要使用单调栈。我们可以令 hi=LCP(i,i1),那么可以得到 hi=minj=sufisufi+1heightj,特殊的,h1=1。这样,我们就可以用单调栈来计算出每个 hi 对答案的贡献,从而计算出 1i<jtLCP(i,j) 的值了。

时间复杂度 O(nlogn+tlog(t))

AC 代码

#include<stdio.h>
#include<string.h>
#include <valarray>
#define int long long
#define N 1000005
#define Mode 23333333333333333
int cnt[N],SA[N],pos[N];
int rank[N],height[N];
char s[N];int n,q;
inline void GetSA(){int m='z';
    memset(cnt,0,sizeof(cnt));
    for(register int i=1;i<=n;++i)
        ++cnt[rank[i]=s[i]];
    for(register int i=1;i<=m;++i)
        cnt[i]+=cnt[i-1];
    for(register int i=n;i;--i)
        SA[cnt[rank[i]]--]=i;
    for(register int k=1;k<=n;k<<=1){
        int num=0;
        for(register int i=n-k+1;i<=n;++i)
            pos[++num]=i;
        for(register int i=1;i<=n;++i)
            if(SA[i]>k) pos[++num]=SA[i]-k;
        memset(cnt,0,sizeof(cnt));
        for(register int i=1;i<=n;++i)
            ++cnt[rank[i]];
        for(register int i=1;i<=m;++i)
            cnt[i]+=cnt[i-1];
        for(register int i=n;i;--i)
            SA[cnt[rank[pos[i]]]--]=pos[i],pos[i]=0;
        std::swap(rank,pos),rank[SA[1]]=1,num=1;
        for(register int i=2;i<=n;++i)
            rank[SA[i]]=
                num+=pos[SA[i]]^pos[SA[i-1]]
                ||pos[SA[i]+k]^pos[SA[i-1]+k];
        if(num==n) break;m=num;
    }
}
inline void GetHeight(){
    for(register int i=1;i<=n;++i)
        rank[SA[i]]=i;
    for(register int i=1,k=0;i<=n;++i){
        if(rank[i]==1) continue;if(k)--k;
        for(register int j=SA[rank[i]-1];
        i+k<=n&&j+k<=n&&s[i+k]==s[j+k];++k);
            height[rank[i]]=k;
    }
}
int mint[N][30],lg[N];
inline void InitST(){lg[0]=-1;
    for(register int i=1;i<=n;++i){
        lg[i]=lg[i>>1]+1;
        mint[i][0]=height[i];
    }for(register int j=1;j<=lg[n];++j)
    for(register int i=1;i+(1<<j)<=n+1;++i)
        mint[i][j]=std::min(mint[i][j-1],
            mint[i+(1<<(j-1))][j-1]);
}
inline int GetMint(int l,int r){
    if(l>r) std::swap(l,r);
    int k=lg[r-l+1];
    return std::min(mint[l][k], 
        mint[r-(1<<k)+1][k]);
}
int sta[N],tail,l[N],r[N];
int p[N],h[N],ans,ce,all;
inline void Query(){
    scanf("%lld",&all);
    for(register int i=1;i<=all;++i)
        scanf("%lld",&p[i]);
    for(register int i=1;i<=all;++i)
        p[i]=rank[p[i]];
    std::sort(p+1,p+all+1);
    ce=std::unique(p+1,p+all+1)-p-1;
    for(register int i=2;i<=ce;++i)
        h[i]=GetMint(p[i-1]+1,p[i]);
    h[1]=0;sta[1]=1;tail=1;ans=0;
    for(register int i=2;i<=ce;++i){
        while(tail&&h[sta[tail]]>h[i])
            r[sta[tail--]]=i;
        l[i]=sta[tail];sta[++tail]=i;
    }while(tail) r[sta[tail--]]=ce+1;
    for(register int i=2;i<=ce;++i){
        ans+=(r[i]-i)*(i-l[i])
        *h[i];
        ans=ans;
    }printf("%lld\n",ans);
}
signed main(){
    scanf("%lld%lld",&n,&q);
    scanf("%s",s+1);
    GetSA();GetHeight();
    InitST();
    while(q--) Query();
}
posted @   UncleSam_Died  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示