bzoj3879 SvT(后缀数组+单调栈)

Description

(我并不想告诉你题目名字是什么鬼)

有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n].

现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍.

Input

第一行两个正整数n,m,分别表示S的长度以及询问的次数.

接下来一行有一个字符串S.

接下来有m组询问,对于每一组询问,均按照以下格式在一行内给出:

首先是一个整数t,表示共有多少个后缀.接下来t个整数分别表示t个后缀在字符串S中的出现位置.

Output

对于每一组询问,输出一行一个整数,表示该组询问的答案.由于答案可能很大,仅需要输出这个答案对于23333333333333333(一个巨大的质数)取模的余数.

Sample Input

7 3
popoqqq
1 4
2 3 5
4 1 2 5 6

Sample Output

0
0
2

Hint

样例解释:

对于询问一,只有一个后缀”oqqq”,因此答案为0.

对于询问二,有两个后缀”poqqq”以及”qqq”,两个后缀之间的LCP为0,因此答案为0.

对于询问三,有四个后缀”popoqqq”,”opoqqq”,”qqq”,”qq”,其中只有”qqq”,”qq”两个后缀之间的LCP不为0,且长度为2,因此答案为2.

对于100%的测试数据,有S<=5*10^5,且Σt<=3*10^6.

特别注意:由于另一世界线的某些参数发生了变化,对于一组询问,即使一个后缀出现了多次,也仅算一次.


分析:
后缀数组不难写,重点在于后面的答案统计
需要用到一个叫“单调栈”的东西:

单调栈与单调队列很相似
首先栈是后进先出的,单调性指的是严格的递增或者递减

单调栈有以下两个性质:

  • 若是单调递增栈,则从栈顶到栈底的元素是严格递增的。若是单调递减栈,则从栈顶到栈底的元素是严格递减的。
  • 越靠近栈顶的元素越后进栈

我们先简单了解一下这种数据结构

对于一组询问,我们ba后缀按照rank排序
要知道,计算两个后缀之间的LCP,实际上就是区间height值的min(自己用心体会一下吧)

我们统计两个相邻的后缀之间LCP的min值(不是很准确,看代码就明白了)

mn=ask(num[i-1]+1,num[i]);

我们维护的单调栈是单调递增的,维护的是当前答案sta[top]
同时维护有多少对后缀的答案等于sta[top]

tip

好像必须用unique去重,因为询问中会有重复的后缀

RMQ的查询不要写错了(整除除法自带向下取整)

//这里写代码片
#include<cstring> 
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long

using namespace std;

const ll mod=23333333333333333LL;
const int N=500010;
int hei[N][20],rak[N];
int cc[N],w1[N],w2[N],len,Q,sa[N],cnt,num[N*10],tot[N],sta[N],top;
char s[N];
ll ans,sum;

int cmp(int *y,int a,int b,int k)
{
    int ra1=y[a];
    int rb1=y[b];
    int ra2=a+k>=len ? -1:y[a+k];
    int rb2=b+k>=len ? -1:y[b+k];
    return ra1==rb1&&ra2==rb2;
}

void make_sa()
{
    int *x=w1,*y=w2;
    int m,i,j,p;
    m=26;
    for (i=0;i<m;i++) cc[i]=0;
    for (i=0;i<len;i++) cc[x[i]=s[i]-'a']++;
    for (i=1;i<m;i++) cc[i]+=cc[i-1];
    for (i=len-1;i>=0;i--) sa[--cc[x[i]]]=i;

    for (int k=1;k<=len;k<<=1)
    {
        p=0;
        for (i=len-k;i<len;i++) y[p++]=i;
        for (i=0;i<len;i++) if (sa[i]>=k) y[p++]=sa[i]-k;
        for (i=0;i<m;i++) cc[i]=0;
        for (i=0;i<len;i++) cc[x[y[i]]]++;
        for (i=1;i<m;i++) cc[i]+=cc[i-1];
        for (i=len-1;i>=0;i--) sa[--cc[x[y[i]]]]=y[i];
        swap(x,y);
        x[sa[0]]=0; p=1;
        for (i=1;i<len;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],k) ? p-1:p++;
        if (p>=len) break;
        m=p;
    }
}

void make_hei()
{
    int k=0;
    for (int i=0;i<len;i++) rak[sa[i]]=i;
    hei[0][0]=0;
    for (int i=0;i<len;i++)
    {
        if (!rak[i]) continue;
        int j=sa[rak[i]-1];
        if (k) k--;
        while (s[i+k]==s[j+k]&&i+k<len&&j+k<len) k++;
        hei[rak[i]][0]=k;
    }
}

void RMQ()
{
    int lg=log(len)/log(2)+1;
    for (int i=1;i<=lg;i++)
        for (int j=0;j<len;j++)
            hei[j][i]=min(hei[j][i-1],hei[j+(1<<(i-1))][i-1]);
}

int ask(int l,int r)
{
    int ln=r-l+1;
    int lg=log(ln)/log(2);
    return min(hei[l][lg],hei[r-(1<<lg)+1][lg]);
}

void solve()
{
    sort(num+1,num+1+cnt);                 //按照rank排序 
    cnt=unique(num+1,num+1+cnt)-num-1;     //询问中会有重复的后缀 
    top=0;
    sum=0; ans=0;   
    for (int i=2;i<=cnt;i++)
    {
        int mn=ask(num[i-1]+1,num[i]);
        int tt=0;
        while (top&&mn<=sta[top])          //单调递增 
        {
            tt+=tot[top];
            sum=((sum-(ll)sta[top]*tot[top])%mod+mod)%mod;   //大于当前的hei,之前统计的答案都无效了 
            top--;
        }
        sta[++top]=mn; tot[top]=tt+1;      //tot表示LCP(i,x)=sta[top]的x数 
        sum=(sum+(ll)sta[top]*tot[top]%mod)%mod;
        ans=(ans+sum)%mod;
    }
    ans=(ans+mod)%mod;
    printf("%lld\n",ans);
}

int main()
{
    scanf("%d%d",&len,&Q);
    scanf("%s",s);
    make_sa();
    make_hei();
    RMQ();
    for (int i=1;i<=Q;i++)
    {
        scanf("%d",&cnt);
        for (int i=1;i<=cnt;i++) 
        {
            int x;
            scanf("%d",&num[i]); 
            num[i]=rak[num[i]-1];
        }

        solve();
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值