P9482 [NOI2023] 字符串 题解

题目描述

\(T\) 组数据,给定长为 \(n\) 的字符串 \(s\)\(q\) 次询问,给定 \(i,r\) ,求有多少个 \(l\) 满足:

  • \(1\le l\le r\)
  • \(s[i:i+l-1]\) 字典序小于 \(R(s[i+l:i+2l-1])\)

数据范围

  • \(1\le T\le 5,1\le n,q\le 10^5,1\le i+2r-1\le n\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{512MB}\)

分析

先考虑如何比较这两个字符串的字典序,容易发现这是后缀数组的拿手好戏。具体的,对 \(s+R(s)\) 建立后缀数组,产生贡献当且仅当下面两个条件同时成立:

  • \(rk_i\lt rk_{2n+2-i-2l}\)
  • \(\texttt{lcp}(i,2n+2-i-2l)\lt l\)

先只考虑第一个条件并进行计数,算上 \(1\le l\le r\) 的限制,本质上是一个二维数点问题。

\((j,rk_j)\) 看成平面上的点,我们需要求出 \(2n+2-i-2r\le x\le 2n-i,y\gt rk_i\) 中的点数,询问离线后按 \(rk\) 倒序枚举 \(i\) ,注意需要对奇偶分别开树状数组。

再考虑第二个条件,我们多算了满足以下条件的 \(l\) ,需要减去:

  • \(1\le l\le r\)
  • \(rk_i\lt rk_{2n+2-i-2l}\)
  • \(\texttt{lcp}(i,2n+2-i-2l)\ge l\),即 \(s[i:i+l-1]=R(s[i+l,i+2l-1])\)

对于第三个条件,先跑 \(\texttt{manacher}\) 算法,枚举回文中心 \(j=i+l-1\),则其等价于 \(l\le o_j\) 。这里 \(o_i\) 表示以第 \(i\) 和第 \(i+1\) 个字符中间为中心,最长非 # 回文半径。

对于第二个条件,由于\(i\) 个后缀和第 \(2n+2-i-2l\) 个后缀的前 \(l\) 个字符相同,根据字典序的比较规则,我们直接忽略掉这 \(l\) 个字符,所以其等价于 \(rk_{j+1}\lt rk_{2n+1-j}\)

现在这个条件只与 \(j\) 有关,它会贡献到满足以下条件的询问:

  • \(1\le j-i+1\le\min(r,o_j)\)

这个条件等价于:

  • \(i\le j\le i+r-1\)
  • \(j-o_j+1\le i\)

再来一遍二维数点即可。

时间复杂度 \(\mathcal O((n+q)\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int m,n,q,t;
int c[maxn],x[maxn],y[maxn];
int rk[maxn],sa[maxn];
int p[maxn],res[maxn];
char s[maxn];
struct node
{
    int x,id,op;
};
vector<node> v1[maxn],v2[maxn];
void get_sa()
{
    for(int i=1;i<=n;i++) s[2*n+1-i]=s[i];
    memset(c,0,sizeof(c));
    memset(y,0,sizeof(y));
    int m=122,n=2*::n;
    for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
    for(int i=1;i<=m;i++) c[i]+=c[i-1];
    for(int i=n;i>=1;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>=1;i--) sa[c[x[y[i]]]--]=y[i];
        for(int i=1;i<=n;i++) y[i]=x[i],x[i]=0;
        x[sa[1]]=num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
        if(num==n) break;
        m=num;
    }
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
}
void manacher()
{
    static char t[maxn];
    int m=2*n+1;
    for(int i=m;i>=1;i--) p[i]=0,t[i]=i&1?'#':s[i>>1];
    for(int i=1,pos=0,mxr=0;i<=m;i++)
    {
        if(i<=mxr) p[i]=min(p[2*pos-i],mxr-i+1);
        while(i+p[i]<=m&&t[i-p[i]]==t[i+p[i]]) p[i]++;
        if(i+p[i]-1>mxr) pos=i,mxr=i+p[i]-1;
    }
}
struct bit
{
    int n,op,c[maxn];
    void clean(int _n,int _op)
    {
        n=_n,op=_op,memset(c,0,4*n);
    }
    void add(int x,int v)
    {
        if(!op) while(x) c[x]+=v,x-=x&(-x);
        else while(x<=n) c[x]+=v,x+=x&(-x);
    }
    int query(int x)
    {
        int res=0;
        if(!op) while(x<=n) res+=c[x],x+=x&(-x);
        else while(x) res+=c[x],x-=x&(-x);
        return res;
    }
}b[3];
int main()
{
    for(scanf("%*d%d",&t);t--;)
    {
        scanf("%d%d%s",&n,&q,s+1),m=2*n;
        get_sa(),manacher();
        b[0].clean(m,0),b[1].clean(m,0),b[2].clean(m,1);
        for(int i=1;i<=m;i++) v1[i].clear(),v2[i].clear();
        for(int j=1,i=0,r=0;j<=q;j++)
        {
            scanf("%d%d",&i,&r),res[j]=0;
            v1[2*n-i].push_back({rk[i]+1,j,1});
            v1[2*n-i-2*r].push_back({rk[i]+1,j,-1});
            v2[i+r-1].push_back({i,j,1});
            v2[i-1].push_back({i,j,-1});
        }
        for(int i=1;i<=m;i++)
        {
            b[i&1].add(rk[i],1);
            for(auto u:v1[i]) res[u.id]+=u.op*b[i&1].query(u.x);
        }
        for(int i=1;i<=n;i++)
        {
            if(rk[i+1]<rk[2*n+1-i]) b[2].add(i-(p[2*i+1]>>1)+1,1);
            for(auto u:v2[i]) res[u.id]-=u.op*b[2].query(u.x);
        }
        for(int i=1;i<=q;i++) printf("%d\n",res[i]);
    }
    return 0;
}

总结

  • 数点条件不好刻画时可以利用字典序的性质进行转化,笔者考场上就差这一步没想到最终 \(72pts\) 含恨回家。
posted @ 2024-08-25 16:40  peiwenjun  阅读(9)  评论(0编辑  收藏  举报