[GDOI2013][JZOJ3277]哈希和

本文深入探讨了哈希算法在解决字符串匹配问题中的应用,并提供了详细的代码实现。通过离线预处理与桶排序技术,有效提高了查询效率。文章详细解释了哈希值的计算方式、哈希值之和的求法,以及如何利用前缀和相减原理计算指定区间内的子串哈希和。此外,还介绍了通过差分数组、桶排序与字符串排序等步骤,实现快速查询排名区间内子串的哈希值之和。代码实现部分涵盖了哈希表初始化、字符串预处理、桶排序与高度获取、哈希值求和与查询处理等多个关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

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

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

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

1|s|,m100000


题目分析

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

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

lr内以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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值