P7409 SvT 题解
前置知识
- 后缀数组;
- height 数组。
题目大意
给出一个字符串 $s$,每次查询 $s$ 若干个后缀两两之间的 LCP 和,答案对 $23333333333333333$ 取模。
解题思路
首先对于 $s$ 求出 $height$ 数组和 $rank$ 数组,接下来我们可以对所有后缀在 $s$ 的后缀排序后的位置,即 $rank_i$ 进行从小到大排序。因为可能存在相同的后缀,所以这里需要离散化一下。那么,这个时候问题即转化为求这些排序后的后缀两两之间的 LCP。
考虑 $height$ 数组的性质:
- 对于排序后的两个后缀 $suffix_i$ 和 $suffix_j$,有 $\displaystyle LCP(suffix_i,suffix_j)=\min_{k=i}^j height_k$;
那么,设每次查询的后缀,排序后起始位置为 $suf_1,\dots,suf_t$,那么相邻两个后缀的 LCP 为 $\displaystyle \min_{j=suf_i}^{suf_{i+1}} height_i$,这个时候我们就可以用 ST 表来快速计算相邻两个后缀的 LCP。
但是,显然我们不能枚举两个后缀然后计算它们的 LCP。这个时候我们就需要使用单调栈。我们可以令 $h_i=LCP(i,i-1)$,那么可以得到 $\displaystyle h_i=\min_{j=suf_i}^{suf_{i+1}} height_j$,特殊的,$h_1=1$。这样,我们就可以用单调栈来计算出每个 $h_i$ 对答案的贡献,从而计算出 $\displaystyle \sum_{1\le i<j\le t} LCP(i,j)$ 的值了。
时间复杂度 $O(n\log n+\sum \mid t\mid \log (\sum \mid t\mid))$。
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();
}

浙公网安备 33010602011771号