字符串哈希

下面介绍的字符串 Hash 函数把一个任意长度的字符串映射成一个非负整数,并且其冲突概率几乎为零。

取一固定值 P,把字符串看做 P 进制数,并分配一个大于零的数值,代表每种字符。 一般来说,我们分配的数值都远小于 P。例如,对于小写字母构成的字符串,可以令 a=1,b=2,,z=26

取一固定值 M,求出该 P 进制数对 M 的余数,作为该字符串的 Hash 值。

通常来说,根据“生日悖论”,在采取单质数作为模数时,元素个数不能超过 O=(P) 个。而采用多个大质数为模数时,元素个数则不能超过 O=(P) 个。而这一数量级远远超过了算法竞赛中可能的规模,因此我们可以认为多模数时是不会发生冲突的。所以,可以认为哈希值相同的字符串就是相同的。

一般来说,我们取 P=131P=13331,此时 Hash值产生冲突的概率极低,只要 Hash值相同,我们就可以认为原字符串是相等的。通常我们取 M=264,即直接使用 unsigned long long 类型存储这个 Hash 值,在计算时不处理算术溢出问题,产生溢出时相当于自动对 264 取模,这样可以避免低效的取模 (mod) 运算。

除了在极特殊构造的数据上,上述 Hash 算法很难产生冲突,一般情况下上述 Hash 算法完全可以出现在题目的标准解答中。我们还可以多取一些恰当的 PM 的值(例如大质数),多进行几组 Hash 运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个 Hash 产生错误的数据。

对字符串的各种操作,都可以直接对 P 进制数进行算术运算反映到 Hash 值上。

如果我们已知字符串 SHash 值为 H(S),那么在 S 后添加一个字符 c 构成的新字符串 S+cHash 值就是H(S+c)=(H(S)×P+value[c])modM。其中乘
P 就相当于 P 进制下的左移运算,value[c] 是我们为 c 选定的代表数值。
如果我们已知字符串 SHash 值为 H(S),字符串 S+THash 值为 H(S+T),那么字符串 THashH(T)=(H(S+T)H(S)×plength(T))modM。这就相当于通过 P 进制下在 S 后边补 0 的方式,把 S 左移到与 S+T 的左端对齐,然后二者相减就得到了 H(T)

例如,S="abc",c="d",T="xyz",则:

S 表示为 P 进制数:1 2 3

H(S)=1P2+2P+3

H(S+C)=1P3+2P2+3P+4=H(S)P+4

S+T 表示为 P 进制数:1 2 3 24 25 26

H(S+T)=1P5+2P4+3P3+24P2+25P+26

SP 进制下左移 length(T) 位:1 2 3 0 0 0

二者相减就是 T 表示为 P 进制数:24 25 26

H(T)=H(S+T)(1P2+2P+3)P3=24P2+25P+26

根据上面两种操作,我们可以通过 O(N) 的时间预处理出字符串所有前缀 Hash 值,并在 O(1) 的时间内查询他的任意子串的 Hash 值。

模数:1e9+7,1e9+9,19260817,998244353,1e7+9,1e7+7,5e5+9
进制数:131,13331

code:

ull hashh(char s[]){
    int l=strlen(s);
    ull num=0;
    for(int i=0;i<l;++i)
        num=num*base+(ull)s[i];
    return num&0x7fffffff;
}

code:

ull f[maxn],bas[maxn];
for(int i=1;i<=n;++i)
{
    f[i]=f[i-1]*base+s[i]-'a'+1;//存储字符串从前往后的哈希值
    bas[i]=bas[i-1]*base;
}
bool check(int l1,int r1,int l2,int r2)
{
    return f[r1]-f[l1-1]*bas[r1-l1+1]==f[r2]-f[l2-1]*bas[r2-l2+1];//比较两段是否相同
}
posted @   「ycw123」  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示