题目大意
现在给出一个由小写字母组成的字符串s,以及m个询问,每个询问给出两个整数x,y(x<=y),表示询问s的所有不同的连续子串中,字典序排名为x到y之间的所有字符串的哈希值的总和(包括x和y)。
解题思路
对于原串求出前缀哈希值的前缀和,26次幂的前缀和,这样我们求可以求出任意区间的前缀哈希值的前缀和。跑一遍sa,求出每个后缀在字典序中排第几,按sa前缀和减去height就可以了。按字典序统计每个后缀在字典序前面的串的哈希和,注意重叠的部分。
我们把询问拆开只用考虑1到x哈希值的总和,先二分出多出来的那个区间,处理一下重叠的部分得到剩余的长度,在那个后缀里面询问一下就可以了。
code
using namespace std;
int const maxn=1e5,mo=12580;
int n,m,cnt[maxn+10],a[2][maxn+10],sa[maxn+10],spow[maxn+10],sum[maxn+10],num[maxn+10],g[maxn+10],h[maxn+10];
int *rank=a[0],*ord=a[1];LL f[maxn+10];
char s[maxn+10];
void sort(){
fo(i,0,m)cnt[i]=0;
fo(i,1,n)cnt[rank[ord[i]]]++;
fo(i,1,m)cnt[i]+=cnt[i-1];
fd(i,n,1)sa[cnt[rank[ord[i]]]--]=ord[i];
}
bool diff(int i,int j,int w){
return (ord[i]!=ord[j])||(ord[i+w]!=ord[j+w]);
}
int count(int l,int r){
return ((sum[r]-sum[l-1]-num[l-1]*spow[r-l+1])%mo+mo)%mo;
}
int count2(LL pos){
if(!pos)return 0;
int tmp=lower_bound(f+1,f+n+1,pos)-f;
return ((g[tmp-1]+count(sa[tmp],sa[tmp]+h[tmp]-1+pos-f[tmp-1])-count(sa[tmp],sa[tmp]+h[tmp]-1))%mo+mo)%mo;//hgljkahfhfh
}
int main(){
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
scanf("%s",s+1);n=strlen(s+1);int tmp=0;spow[0]=1;
fo(i,1,n){
num[i]=(num[i-1]*26+s[i]-'a')%mo;
sum[i]=(sum[i-1]+num[i])%mo;
spow[i]=(spow[i-1]*26+1)%mo;
}
fo(i,1,n)rank[i]=s[i],ord[i]=i;m='z';
sort();
for(int w=1,p=1;p<n;w=w<<1){
m=p;p=0;fo(i,n-w+1,n)ord[++p]=i;
fo(i,1,n)if(sa[i]>w)ord[++p]=sa[i]-w;
sort();swap(rank,ord);rank[sa[1]]=p=1;
fo(i,2,n)rank[sa[i]]=(p+=diff(sa[i],sa[i-1],w));
}
for(int i=1,p=0,j;i<=n;h[rank[i]]=p,i++)
for(p=p-(p!=0),j=sa[rank[i]-1];s[i+p]==s[j+p];p++);
fo(i,1,n){
f[i]=f[i-1]+n-sa[i]+1-h[i];
g[i]=((g[i-1]+count(sa[i],n)-count(sa[i],sa[i]+h[i]-1))%mo+mo)%mo;
}
LL x,y;int q;scanf("%d",&q);
fo(i,1,q){
scanf("%lld%lld",&x,&y);
printf("%d\n",((count2(y)-count2(x-1))%mo+mo)%mo);
}
return 0;
}