【jzoj3277】【GDOI2013】【哈希和】【后缀数组】

23 篇文章 1 订阅

题目大意

现在给出一个由小写字母组成的字符串s,以及m个询问,每个询问给出两个整数x,y(x<=y),表示询问s的所有不同的连续子串中,字典序排名为x到y之间的所有字符串的哈希值的总和(包括x和y)。

解题思路

对于原串求出前缀哈希值的前缀和,26次幂的前缀和,这样我们求可以求出任意区间的前缀哈希值的前缀和。跑一遍sa,求出每个后缀在字典序中排第几,按sa前缀和减去height就可以了。按字典序统计每个后缀在字典序前面的串的哈希和,注意重叠的部分。
我们把询问拆开只用考虑1到x哈希值的总和,先二分出多出来的那个区间,处理一下重叠的部分得到剩余的长度,在那个后缀里面询问一下就可以了。

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define min(a,b) ((a<b)?a:b)
#define max(a,b) ((a>b)?a:b)
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值