字符串哈希
下面介绍的字符串 \(\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];//比较两段是否相同
}