[GDOI2013][JZOJ3277]哈希和

题目大意

设字符串 str 长度为 l ,定义字符串 s 的哈希值为

hash(str)=i=0l1c(stri)×26l1i

其中 c(stri) 表示字符 stri ASCII 码与字符 a ASCII 码的差值。
给定字符串 s ,有 m 个询问,查询所有不重复的子串中,排名第 x 的到排名第 y 的子串哈希值之和,保证存在这么多不同的子串。

1|s|,m100000

题目分析

本题的难点在于如何求出哈希值的和,其它的我们直接离线,将询问差分,然后存在一个桶里面,排个序,求答案时我们直接按照排序后的顺序扫一遍,将可处理的询问处理了。
那么哈希值之和怎么求呢?
我们定义如下数组

hashi=hash(s0..i)sumi=sumi1+hashipi=j=1i+126j

l r 内以 l 开头的子串的哈希和就为
sumrsuml1prlhashl1

因为前缀和相减之后,剩下的值就是原串最左端到区间内所有位置组成的的子串的哈希和,显然和这个区间最左端开始的子串多出同样的字符串,都是原串最左端到区间最左端的左端组成的子串,那么我们将他乘上差的几位幂数和,减掉就行了。
具体细节请读者自行思考,详见代码实现。

代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long LL;

const int N=100050;
const int M=100050;
const int P=12580;
const int D=26;

struct Q
{
    LL K;
    int id;
}qu[N<<1];

bool operator<(Q a,Q b)
{
    return a.K<b.K;
}

int Ws[N],Wv[N],x[N],y[N],SA[N],rank[N],height[N],sum[N],p[N],hash[N],ans[M][3];
bool deal[M];
LL q[M][2];
char s[N];
int n,m;

void pre()
{
    p[0]=D;
    for (int i=1,j=D*D%P;i<=n;i++,(j*=D)%=P)
        p[i]=(p[i-1]+j)%P;
    hash[0]=s[0]-'a';
    for (int i=1;i<n;i++)
        hash[i]=((LL)hash[i-1]*D%P+(s[i]-'a'))%P;
    sum[0]=hash[0];
    for (int i=1;i<n;i++)
        sum[i]=(sum[i-1]+hash[i])%P;
}

bool cmp(int *r,int i,int j,int l)
{
    return r[i]==r[j]&&r[i+l]==r[j+l];
}

void DA()
{
    int mx=0,l=1,p=0,i;
    for (i=0;i<n;i++)mx=max(mx,Wv[i]=x[i]=s[i]-'a');
    for (i=0;i<=mx;i++)Ws[i]=0;
    for (i=0;i<n;i++)Ws[Wv[i]]++;
    for (i=1;i<=mx;i++)Ws[i]+=Ws[i-1];
    for (i=n-1;i>=0;i--)SA[--Ws[Wv[i]]]=i;
    for (;l<=n&&p!=n;l<<=1)
    {
        for (p=0,i=n-l;i<n;i++)y[p++]=i;
        for (i=0;i<n;i++)if(SA[i]>=l)y[p++]=SA[i]-l;
        for (mx=0,i=0;i<n;i++)mx=max(mx,Wv[i]=x[y[i]]);
        for (i=0;i<=mx;i++)Ws[i]=0;
        for (i=0;i<n;i++)Ws[Wv[i]]++;
        for (i=1;i<=mx;i++)Ws[i]+=Ws[i-1];
        for (i=n-1;i>=0;i--)SA[--Ws[Wv[i]]]=y[i];
        for (i=0;i<n;i++)y[i]=x[i],x[i]=0;
        for (p=0,x[SA[0]]=0,i=1;i<n;i++)x[SA[i]]=cmp(y,SA[i-1],SA[i],l)?p:++p;
    }
    for (i=0;i<n;i++)rank[SA[i]]=i;
}

void get_height()
{
    for (int k=0,i=0;i<n;i++)
    {
        k?k--:k;
        if (!rank[i])
            continue;
        int j=SA[rank[i]-1];
        while (i+k<n&&j+k<n&&s[i+k]==s[j+k])
            k++;
        height[rank[i]]=k;
    }
}

int hashsum(int st,int en)
{
    if (st>en)
        return 0;
    return (((LL)sum[en]-(st?sum[st-1]:0)-(LL)(st?hash[st-1]:0)*p[en-st]%P)%P+P)%P;
}

int prefixsum(int st,int en1,int en2)
{
    return ((hashsum(st,en2)-hashsum(st,en1-1))%P+P)%P;
}

void calc()
{
    int ptr=1,cnt=0;
    LL kth=0,las=0;
    for (int i=0;i<n;i++)
    {
        las=kth;
        kth+=n-SA[i]-height[i];
        while (ptr<=m<<1&&qu[ptr].K<=kth)
        {
            int j=qu[ptr].id,l=deal[j]?2:1;
            deal[j]=1;
            if (!qu[ptr].K)
                ans[j][l]=0;
            else
                ans[j][l]=(cnt+prefixsum(SA[i],SA[i]+height[i],SA[i]+height[i]+qu[ptr].K-las-1))%P;
            ptr++;
        }
        cnt=(cnt+prefixsum(SA[i],SA[i]+height[i],n-1))%P;
    }
    for (int i=1;i<=m;i++)
        ans[i][0]=((ans[i][2]-ans[i][1])%P+P)%P;
}

int main()
{
    freopen("hashsum.in","r",stdin);
    freopen("hashsum.out","w",stdout);
    scanf("%s",s);
    n=strlen(s);
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%lld%lld",&q[i][0],&q[i][1]);
        qu[(i<<1)-1].K=q[i][0]-1,qu[i<<1].K=q[i][1];
        qu[(i<<1)-1].id=i,qu[i<<1].id=i;
    }
    sort(qu+1,qu+1+(m<<1));
    pre();
    DA();
    get_height();
    calc();
    for (int i=1;i<=m;i++)
        printf("%d\n",ans[i][0]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

你可能感兴趣的:(字符串,哈希,后缀数组,OI,GDOI)