Hash在信息学竞赛中的一类应用Word文件下载.docx
- 文档编号:13609207
- 上传时间:2022-10-12
- 格式:DOCX
- 页数:10
- 大小:82.97KB
Hash在信息学竞赛中的一类应用Word文件下载.docx
《Hash在信息学竞赛中的一类应用Word文件下载.docx》由会员分享,可在线阅读,更多相关《Hash在信息学竞赛中的一类应用Word文件下载.docx(10页珍藏版)》请在冰豆网上搜索。
本文要讨论的,就是这一类以判重或判等价为目标的Hash函数。
让我们来看看例题1。
例题1多维匹配
题目大意
在一个串中求另一个串第一次出现的位置,很简单,KMP即可。
扩展到二维情况,就是求在一个矩阵中求另一个矩阵第一次出现的位置。
而如果扩展到k维的情况,又该怎么做呢?
待匹配数组X各维的尺寸为N1,N2,…,Nk,模式数组Y各维的尺寸为M1,M2,…,Mk。
记N=N1N2…Nk,M=M1M2…Mk。
保证k≤10,Ni≥Mi,而N和M都不超过500000。
算法分析
本题常见的算法是多维情况的KMP,先算1维时的匹配情况,然后处理2维,3维……直到N维时的情况。
时间复杂度为O(k*(N+M))。
但是它难以理解和记忆,也不容易在比赛中的短短几个小时完成和写对。
朴素的解法相对易于实现,但是使朴素算法很容易想到,而且针对数据又很容易制作,因此只有在完全没有思路的时候才值得一试。
有没有第三种选择呢?
答案是肯定的。
朴素的解法相当于枚举了每个起点(起点数量显然不会超过N)并加以判断,然而判断两个子数组是否相同的时间复杂度高达O(M),这就是导致朴素算法在遇到针对数据的情况下很慢的原因。
能不能在比较两个子数组之前先快速地排除一些明显不可能的情况呢?
由于很容易构造出仅有一个字符不同的数据,不管采用什么顺序比较都会消耗大量的时间。
Hash函数在这里派上了用场。
我们可以对每个尺寸与模式数组一样的子数组计算一个Hash值。
显然,只有当某个子数组的Hash值与模式数组的Hash值一样,它们才值得比较。
多维的KMP要从1维的情况开始考虑,并推广至高维。
那么这种计算Hash函数的匹配算法我们也可以先考虑一维的情况——就是Rabin-Karp算法。
对于一个字符串S,可以使用这个函数求出其Hash值:
那么,模式串Y的Hash值就可以轻松地求出。
记待匹配串X从第i个字符开始的长度为M的子串为Si,则不难发现f(Si+1)和f(Si)的关系:
换个角度,如果不考虑modq,这个函数就是把字符串看作一个p进制数求出的值,这样,Xi+1就是Xi“左移”一位,然后去掉最前面一位,再加上右面新进来的一位得到的。
因此上面的递推公式也是显然的。
有了这个递推公式,不难在线性时间内求出X的所有长度为M的子串的Hash值。
现在把问题扩展到高维的情况。
不难发现要计算的Hash值的个数仍然不超过N个,可见,只要有合适的递推方法,仍然可以在线性时间内求出所有子矩阵的Hash值。
事实上,一个M1行,M2列的子矩阵可以被看作是M2个“竖条”组成的一个串。
我们可以先把每个长度为M1的“竖条”计算出一个Hash值,然后再计算二维情况的Hash值。
需要注意的一点是,在计算一维的Hash值(“竖条”的Hash值)和计算二维的Hash值时使用的b值不能一样,不然不难想到下面反例:
它们并不相同,但是Hash的结果肯定一样。
这就违背了使用Hash的初衷。
因此,在第一维的计算和第二维的计算中要使用不同的p值。
二维情况时,求出所有长度为M1的“竖条”所需要花费的时间是O(N)的,然后把这些竖条的Hash值看作“字母”计算横向的Hash值(即各个子矩阵的Hash值),所花费的时间也是O(N)的。
总时间复杂度仍然是O(N)的。
二维情况的Hash函数为(len1(S)和len2(S)分别表示S在两个维度上的大小):
类似地,记待匹配矩阵X从第i1行,第i2列为左上角的M1行,M2列的子矩阵为,我们有递推式:
式中和都是一维情况下的Hash值,即预先计算出的“竖条”的Hash值。
扩展到k维情况,则不难想到,先算出所有尺寸为M1×
M2×
…×
Mk-1的子数组的Hash值,再使用类似上面的办法把这些尺寸为M1×
Mk-1的子数组的Hash值看作一个个字符而使用Rabin-Karp的Hash函数计算出各个尺寸为M1×
Mk的子数组的Hash值。
可见,需要k轮计算就可以算出所有尺寸为M1×
Mk的子数组的Hash值,时间复杂度为O(kN+M)。
k维时的Hash函数:
类似地可以推出递推式
如果没有最后的modq操作,那么这个Hash函数可以理解成一个进制转换,当每次的p都取得比当时的字符集还要大的时候,只要Hash值不同就一定能确定内容不同。
但是事实上计算出来的Hash值就会大到使这个算法没有意义的程度(比较这个“Hash值”的速度不会比直接比较更快),因此取余是必须的。
一个理想情况下的Hash函数,如果能产生S种不同的值,那么对两个不一样的子数组算出一样的值的可能性就只有1/S。
我们的Hash函数当然未必能达到理想情况,但是由“1/S”这个式子不难想到,加大S就能有效地减少判错的可能性。
并且还能得出另一个结论:
如果一个Hash函数的精确程度不够,只需要再计算一个与之不同的Hash函数,就可以有效地提高正确率!
当然,在本题中,时间复杂度已经近似于(因为毕竟这个Hash函数不是理想的Hash函数)O(kN+N*(1/S)*M)了。
S只要大小不比N/k小,后一项就不是时间复杂度的瓶颈了,也没有必要在这方面下太大功夫。
反而,如果计算多个Hash函数,会显著增加算法的常数。
关于p和q的取值,由于可以理解成一个p进制的转换,再对q取余,应当在范围允许的情况下尽量避免冲突,建议:
1、p不宜过小,不然很容易出错。
2、q可以取一个质数,然后p选取一个能使对于所有1至p-2的i,pimodq≠1。
这个Hash函数不但可以这样滚动计算,也可以采用分段,分块,线段树等办法计算和维护,具体的一些办法可以参见后面的一些例题。
例题2Equalsquares(Ural1486)
在一个N×
M的字符矩阵中找到两个相同的子正方形矩阵(可以相交),并使找到的两个子正方形矩阵的边长尽量大。
有了例题1的经验,在面对本题的时候不难想到,二分查找正方形的边长,然后使用一个Hash表来判断该边长是否可行。
Hash函数与例题1的二维情况一致。
时间复杂度是O(NMlog(N*M))的。
但是不幸的是,本题时间限制很严,这样做的程序依然会超时。
最后在使用一个看上去有点冒险的改动之后,终于通过了本题:
直接检查是否产生了相同的Hash函数,如果存在相同的Hash函数,则认为该边长可行,否则即认为它不可行。
这道题目就这样通过了,但是这样的改动是不是太冒险了一点呢?
事实上,两个子正方形的内容不同而Hash值相同的可能性很小。
所以,一般情况下我们可以认为,如果Hash值相同,则原内容相同。
这里还有一个问题:
Hash表存放了什么?
如果存放的是Bool而不使用位压缩,则似乎浪费了不少空间,而且Hash表不可能开到很大,因此两个不同的内容Hash到同一个位置还是有可能的,简单地比较“这个位置是否被Hash到”仍然不算保险,毕竟,一次查询有100万分之一的可能性出错的话,100万次查询出错概率就很可观了。
这里介绍的一个小技巧是计算两个Hash值,一个用于确定Hash表中的位置,另一个则用于比较。
这样,只有如果在Hash表的某个地址开始查找找到了一个Hash值与自己的Hash值一样的元素,才认为自己在Hash表中出现过。
例题3不稳定匹配
有两个串A和B,每次可以进行的操作有:
INSERTij:
在A中第i个字符前插入j。
DELETEi:
删除A中第i个字符。
REVERSEij:
把A中i到j之间的内容反转。
QUERYij:
求A从第i个字符开始,B从第j个字符开始能匹配的最大长度,即询问A从第i个字符开始的后缀与B从第j个字符开始的后缀的LCP长度。
如果两个串不会变化,求LCP只需要求出A+B的后缀数组即可。
但是本题的A串是不断变化的,而且由变化的方式可以看出,每次操作都会导致后缀数组发生很大的变化,因此我们应该另辟蹊径。
对于一个k,不难判断两个串的LCP是否有至少k个字符:
计算这两个串的前k个字符的Hash值,并且比较它们是否相等。
如果相等,就几乎可以肯定地认为这两个串的LCP至少有k个字符,否则,它们的LCP长度肯定不到k。
这样,就可以通过二分查找来计算LCP。
现在问题又转化成了怎么求一个子串的Hash。
不妨仍然采用Rabin-Karp的Hash函数,如果已知了S1和S2的Hash值,不难求出S1+S2的Hash值:
而的Hash值显然又是可以在O(n)时间内预处理的得到的。
因此,可以在O
(1)时间内通过两个字符串的Hash值得到它们连接后得到的串的Hash值。
因此,可以使用块状链表维护计算A中子串的Hash值,方法于维护计算部分和类似,不同之处在于一个字符串正向和反向的Hash值是不同的,为了能在O(n0.5)时间内完成reverse操作,应当要能在O
(1)时间内把一个块“反转”,这就要求我们为一个块维护两个Hash值:
一个是正向的,一个是倒向的。
除此之外的操作于维护部分和或者维护最大值类似。
这样,插入,删除,反转操作是O(n0.5)的,而查询操作是O(n0.5logn)的。
类似地,本题的另一种解法是在一棵splay树上维护Hash值。
每次一个节点被旋转或以它为根的子树被修改时,则计算它的正向Hash值和反向Hash值,这样,就可以在O
(1)时间你reverse一棵子树,通过split可以不难地把一棵子树在均摊O(logn)时间内反转。
插入,删除操作显然也是均摊O(logn)的,而查询操作的均摊时间复杂度为O(log2n)。
例题4一类同构判定的问题
问题1:
比较两棵树是否相同。
不难想到的算法是使用两个字符串分别表示两棵树,但是如果使用Hash的话应该怎么做呢?
可以使用一种类似树状递推的方法来计算Hash值:
对于一个节点v,先求出它所有儿子节点的Hash值,并从小到大排序,记作H1,H2,…,HD。
那么v的Hash值就可以计算为:
换句话说,就是从某个常数开始,每次乘以p,和一个元素异或,再除以q取余,再乘以p,和下一个元素异或,除以q取余……一直进行到最后一个元素为止。
最后把所得到的结果乘以b,再对q取余。
之所以事先对儿子的Hash值进行排序,是因为仅仅儿子的顺序不同并不会导致树的不同,而后面如何Hash就比较随意了,即使不用这个Hash函数,只要使用一个效果足够好的Hash函数也是可以的。
但是要注意,诸如Rabin-Karp的那个Hash函数就不适合在这里应用,因为不难找到反例:
考虑右边的树的儿子的顺序如图所示的情况(可以认为Hash函数的大小是随机分布的,因此有一半的可能性出现这种情况),由于叶子节点的Hash值必然相同(因为都是等价的),不妨记作l,然后递推关系是:
则左边的树的Hash值为:
右边为:
不难
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Hash 信息学 竞赛 中的 一类 应用