字符串哈希

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

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

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

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

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

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

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

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

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

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

\(H(S)=1 \cdot P^2+2 \cdot P+3\)

\(H(S+C)=1 \cdot P^3+2 \cdot P^2+3 \cdot P+4=H(S) \cdot P+4\)

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

\(H(S+T)=1 \cdot P^5+2 \cdot P^4+3 \cdot P^3+24 \cdot P^2+25 \cdot P+26\)

\(S\)\(P\) 进制下左移 \(length(T)\) 位:1 2 3 0 0 0

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

\(H(T)=H(S+T)-(1 \cdot P^2+2 \cdot P+3) \cdot P^3=24 \cdot P^2+25 \cdot P+26\)

根据上面两种操作,我们可以通过 \(\mathcal O(N)\) 的时间预处理出字符串所有前缀 \(\rm Hash\) 值,并在 \(\mathcal O(1)\) 的时间内查询他的任意子串的 \(\rm 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 @ 2022-10-06 16:26  「ycw123」  阅读(40)  评论(0编辑  收藏  举报