字符串哈希
-
定义 f ( s ) f(s) f(s),表示字符串 s s s映射到整数的函数。 f f f为哈希函数。
-
哈希函数有两个性质
-
在 Hash 函数值不一样的时候,两个字符串一定不一样;
-
在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。
-
Hash 函数值一样时原字符串却不一样的现象我们成为哈希碰撞。
-
定义哈希函数公式等于(定义 l e n ( s ) len(s) len(s)表示字符串 s s s的长度) f ( s ) = ∑ i = 1 l e n ( s ) i n t ( s [ i ] ) ∗ b a s e l e n ( s ) − i % M f(s)=\sum_{i=1}^{len(s)}int(s[i])*base^{len(s)-i}\%M f(s)=∑i=1len(s)int(s[i])∗baselen(s)−i%M简单来说,比如字符串abc,(其中我们令a=1,依次往后推)我们定义 f ( s ) = ( b a s e 3 ∗ 1 + b a s e 2 ∗ 2 + b a s e 1 ∗ 3 ) % M f(s)=(base^3*1+base^2*2+base^1*3)\%M f(s)=(base3∗1+base2∗2+base1∗3)%M可以类比二进制,这里是base进制。
-
这里 M M M需要一个质数,哈希碰撞的概率为 ( l − 1 ) / M (l-1)/M (l−1)/M所以应该尽可能选择大的模数。
-
代码实现(这里使用unsigned long long 来实现取模,c++自带的unsigned long long自动取模 2 63 − 1 2^{63}-1 263−1,可以看到哈希冲突的概率基本为零。)
#define unsigned long long ull
ull Hash[N],poww[N],base[N];
string s;
ull base=233333
void init()
{
Hash[0] = 0;
poww[0] = 1;
upd(i, 1, len)
{
Hash[i] = Hash[i - 1] * base + s[i] - 'a';
}
upd(i, 1, len)poww[i] = poww[i - 1] * base;
}
ull querry(int l, int r)
{
return Hash[r] - Hash[l - 1] * (poww[r - l + 1]);
}
注意一下代码的细节。针对查询操作。比如我们要对比两个字符川在[L,R]区间是不是一样的。由于 f ( s ) f(s) f(s)函数的性质,哈希[L,R]区间的函数值 v a l = f ( r ) − f ( l ) ∗ b a s e r − l + 1 val=f(r)-f(l)*base^{r-l+1} val=f(r)−f(l)∗baser−l+1。
为什么呢。比如abcd的哈希值
f ( s = = a b c d ) = b a s e 4 ∗ a + b a s e 3 ∗ b + b a s e 2 ∗ c + b a s e 1 ∗ d f(s==abcd)=base^{4}*a+base^{3}*b+base^{2}*c+base^{1}*d f(s==abcd)=base4∗a+base3∗b+base2∗c+base1∗d。
如果我们计算区间[2,3]的哈希值, f ( s = = a b c ) = b a s e 3 ∗ a + b a s e 2 ∗ b + b a s e 1 ∗ c f(s==abc)=base^{3}*a+base^{2}*b+base^{1}*c f(s==abc)=base3∗a+base2∗b+base1∗c
然后是 f ( s = = a ) = b a s e 1 ∗ a f(s==a)=base^{1}*a f(s==a)=base1∗a那么区间[2,3]的哈希值就等于 f ( s = = a b c ) − f ( s = = a ) ∗ b a s e 2 = b a s e 3 ∗ a + b a s e 2 ∗ b + b a s e 1 ∗ c − b a s e 1 ∗ a ∗ b a s e 2 = b a s e 2 ∗ b + b a s e 1 ∗ c f(s==abc)-f(s==a)*base^{2}=base^{3}*a+base^{2}*b+base^{1}*c-base^{1}*a*base^{2}=base^{2}*b+base^{1}*c f(s==abc)−f(s==a)∗base2=base3∗a+base2∗b+base1∗c−base1∗a∗base2=base2∗b+base1∗c
这就是哈希字符串bc的哈希值。也就是 f ( b c ) = = f ( r ) − f ( l ) ∗ b a s e r − l + 1 = = f ( s = = a b c ) − f ( s = = a ) ∗ b a s e 2 f(bc)==f(r)-f(l)*base^{r-l+1}==f(s==abc)-f(s==a)*base^{2} f(bc)==f(r)−f(l)∗baser−l+1==f(s==abc)−f(s==a)∗base2。
依据这个性质,我们能够对比任意等长字符串是不是同一个字符传。
入门题目 :https://www.luogu.com.cn/problem/P3370、
1.值得注意的是,尽管这样做哈希冲突的概率极小,但仍然有可能。所以可以使用两个哈希函数。选取不同的base即可。不放心还可以同时选取不同的base和模数。然后每一次比较哈希值的时候,两个同时比较。只有两个同时向邓,才能表示这两个字符串的哈希值相等。
2.不一定只有字符串才能用这种哈希,只要是区间对比是不是一样的题目,或者其他变形题目,都可以用以上的方法,进行哈希然后O(1)复杂度判断是否相等。