昨天做了学长拉的字符串哈希的专题,今天便来做个总结吧。
哈希是数据结构里面的内容,其实就是把字符串编个号,用数字来表示,这样查询和判断相同时就方便了许多。
但是编的这个号也是有很关键,我们需要尽可能的保证不同字符串的编号不会相等,即冲突概率为0.
一个关键的定理:哈希值相等的字符串不一定相等,字符串相等的哈希值一定相等。因此我们需要尽可能减少第一种情况。
在处理子串问题时,我们一般采用前缀和去维护哈希值(当然,你可以使用树状数组啊,平衡树啊,去动态维护整个序列),因为它是比较简单易于实现的哈希函数(当然也有其他哈希函数,详情请参考:字符串哈希函数)
下面的讲解都是根据前缀和来实现的。
常用的哈希方法有三种:自然溢出,单哈希和双哈希。
一:自然溢出
自然溢出其实就是用unsigned long long来代替了对质数取余这一步骤,unsigned long long本身范围就是2^64-1,也相当与对一个大质数取余了,其实到了一样。不过因为简单易于实现,最常用这种方法来实现。
先声明hash[maxn]的类型为unsigned long long,哈希函数hash[i]=hash[i-1]*p+s[i]-'a'+1(s为字符串)
二:单哈希
单哈希是比较经典的哈希方法,hash[i]=(hash[i-1]*p+s[i]-'a'+1)%mod(p,mod为质数,而且p<mod,p与mod取尽可能大的质数,减少冲突概率)
三:双哈希
顾名思义,用两个哈希数组hash1,hash2和两个mod1,mod2来实现,结果用一个二元组表示
hash1[i]=(hash1[i−1])∗p+s[i]-'a'+1) % mod1
hash2[i]=(hash2[i−1])∗p+s[i]-'a'+1) % mod2
hash结果为<hash1[n],hash2[n]>
(备注:为什么要乘于p,相当于把字符串看成一个p进制的数,类比16进制。哈希值就是进制转换之后对mod取余的结果)
下面讲一个哈希算法的经典用法,就是取出字符串的子串的哈希值。
先抛出代码!!
hash[l~r] = hash[1~r] - hash[1~l - 1] * pow(P, r - l + 1);
看起来这个式子人畜无害,但是对于取模运算要谨慎再谨慎,注意到括号里面是减法,即有可能是负数,故做如下的修正: hash=((hash[r]−hash[l−1]∗pr−l+1)%MOD+MOD)%MOD;
至此得到求子串hash值公式。
当然处理的时候,求p的多少次方,我们都会预处理一下,这个代码的思想大家理解一下就应该会懂。
下面给出证明:(来自网上大佬)
例子
假设有一个|S|=5的字符串,设Si为第i个字符,其中1≤i≤5。
根据定义分别求出hash[i]
hash[1]=s1
hash[2]=s1∗p+s2
hash[3]=s1∗p2+s2∗p+s3
hash[4]=s1∗p3+s2∗p2+s3∗p+s4
hash[5]=s1∗p4+s2∗p3+s3∗p2+s4∗p+s5
现在我们想求s3s4的hash值,不难得出为s3∗p+s4,并且从上面观察,
如果看hash[4]−hash[2]并将结果种带有s1,s2系数的项全部消掉,就是所求。
但是由于p的阶数,不能直接消掉,所以问题就转化成,将hash[2]乘一个关于p的系数,
在做差的时候将多余项消除,从而得到结果。
不难发现,对应项系数只差一个p2,而4 - 3 + 1 = 2(待求hash子串下标相减再加一),
这样就不难推导出来此例题的求解式子。
hash[4]−hash[2]∗p^(4−2+1)
关于哈希算法的好的板子,大家可以网上百度一下kuangbin大佬的板子,我感觉挺不错的。
单独考哈希的题出的很少,而且有的其他方法也可以实现,不过哈希还是很有用的,是其他的基础。
哈希常常和二分一起出现,后面的题也有。
下面抛出一些经典的哈希题。
hdu1880——哈希+恶心输入输出 | 题解 |
POJ1743——哈希+二分(后缀数组SA) | 题解 |
POJ3461——经典哈希(求可重叠串个数) | 题解 |
POJ2406——经典哈希(求最大循环次数) | 题解 |
POJ2752——经典哈希(公共前缀后缀长度) | 题解 |
SCU4438——经典哈希+前缀和 | 题解 |
hdu1496——哈希妙用(暴力+优化) | 题解 |
终于搞完了!!!!