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;
}