ImageVerifierCode 换一换
格式:DOCX , 页数:17 ,大小:178.21KB ,
资源ID:9978902      下载积分:12 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/9978902.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Hash在信息学竞赛中的一类应用.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Hash在信息学竞赛中的一类应用.docx

1、Hash在信息学竞赛中的一类应用Hash在信息学竞赛中的一类应用【介绍】Hash表作为一种高效的数据结构,有着广泛的应用。如果Hash函数设计合理,理想情况下每次查询的时间花费仅仅为O(h/r),即和Hash表容量与剩余容量的比值成正比。只要Hash表容量达到实际使用量的大约1.5倍以上,查询花费的时间基本就可以认为恒为O(1)。【正文】Hash表作为一种高效的数据结构,有着广泛的应用。如果Hash函数设计合理,理想情况下每次查询的时间花费仅仅为O(h/r),即和Hash表容量与剩余容量的比值成正比。只要Hash表容量达到实际使用量的大约1.5倍以上,查询花费的时间基本就可以认为恒为O(1)。

2、对于一个Hash表,一个好的Hash函数是尤其重要的,因为它能使Hash表保证效率。一个好的Hash函数最显而易见的特征是,能使不相同的东西经过Hash之后只有很小的几率相同。这样能避免过多冲突的产生。Hash表离不开Hash函数,但是反过来呢?有的时候,Hash函数却是可以离开Hash表的。一个常见的例子就是著名的MD5算法,它是一个Hash函数,但是它的用途往往是对信息进行加密,以验证信息的正确性。换句话说,我们事实上是通过直接比较MD5算出的结果是否相同以推断原文内容是否一致。除了MD5,常用的CRC32校验码和SHA-1算法也是基于类似的想法而产生的。那么,信息学竞赛中,这样的算法有没

3、有用武之地呢?本文要讨论的,就是这一类以判重或判等价为目标的Hash函数。让我们来看看例题1。例题1 多维匹配题目大意在一个串中求另一个串第一次出现的位置,很简单,KMP即可。扩展到二维情况,就是求在一个矩阵中求另一个矩阵第一次出现的位置。而如果扩展到k维的情况,又该怎么做呢?待匹配数组X各维的尺寸为N1,N2,Nk,模式数组Y各维的尺寸为M1,M2,Mk。记N=N1N2Nk ,M=M1M2Mk 。保证k10,NiMi,而N和M都不超过500000。算法分析本题常见的算法是多维情况的KMP,先算1维时的匹配情况,然后处理2维,3维直到N维时的情况。时间复杂度为O(k*(N+M)。但是它难以理解

4、和记忆,也不容易在比赛中的短短几个小时完成和写对。朴素的解法相对易于实现,但是使朴素算法很容易想到,而且针对数据又很容易制作,因此只有在完全没有思路的时候才值得一试。有没有第三种选择呢?答案是肯定的。朴素的解法相当于枚举了每个起点(起点数量显然不会超过N)并加以判断,然而判断两个子数组是否相同的时间复杂度高达O(M),这就是导致朴素算法在遇到针对数据的情况下很慢的原因。能不能在比较两个子数组之前先快速地排除一些明显不可能的情况呢?由于很容易构造出仅有一个字符不同的数据,不管采用什么顺序比较都会消耗大量的时间。Hash函数在这里派上了用场。我们可以对每个尺寸与模式数组一样的子数组计算一个Hash

5、值。显然,只有当某个子数组的Hash值与模式数组的Hash值一样,它们才值得比较。多维的KMP要从1维的情况开始考虑,并推广至高维。那么这种计算Hash函数的匹配算法我们也可以先考虑一维的情况就是Rabin-Karp算法。对于一个字符串S,可以使用这个函数求出其Hash值:那么,模式串Y的Hash值就可以轻松地求出。记待匹配串X从第i个字符开始的长度为M的子串为Si,则不难发现f(Si+1)和f(Si)的关系: 换个角度,如果不考虑mod q,这个函数就是把字符串看作一个p进制数求出的值,这样,Xi+1就是Xi “左移”一位,然后去掉最前面一位,再加上右面新进来的一位得到的。因此上面的递推公式

6、也是显然的。有了这个递推公式,不难在线性时间内求出X的所有长度为M的子串的Hash值。现在把问题扩展到高维的情况。不难发现要计算的Hash值的个数仍然不超过N个,可见,只要有合适的递推方法,仍然可以在线性时间内求出所有子矩阵的Hash值。事实上,一个M1行,M2列的子矩阵可以被看作是M2个“竖条”组成的一个串。我们可以先把每个长度为M1的“竖条”计算出一个Hash值,然后再计算二维情况的Hash值。需要注意的一点是,在计算一维的Hash值(“竖条”的Hash值)和计算二维的Hash值时使用的b值不能一样,不然不难想到下面反例:它们并不相同,但是Hash的结果肯定一样。这就违背了使用Hash的初

7、衷。因此,在第一维的计算和第二维的计算中要使用不同的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维情况,则不难想到,先算出所有尺寸为M1M2Mk-1的子数组的Has

8、h值,再使用类似上面的办法把这些尺寸为M1M2Mk-1的子数组的Hash值看作一个个字符而使用Rabin-Karp的Hash函数计算出各个尺寸为M1M2Mk的子数组的Hash值。可见,需要k轮计算就可以算出所有尺寸为M1M2Mk的子数组的Hash值,时间复杂度为O(kN+M)。k维时的Hash函数: 类似地可以推出递推式如果没有最后的mod q操作,那么这个Hash函数可以理解成一个进制转换,当每次的p都取得比当时的字符集还要大的时候,只要Hash值不同就一定能确定内容不同。但是事实上计算出来的Hash值就会大到使这个算法没有意义的程度(比较这个“Hash值”的速度不会比直接比较更快),因此取

9、余是必须的。一个理想情况下的Hash函数,如果能产生S种不同的值,那么对两个不一样的子数组算出一样的值的可能性就只有1/S。我们的Hash函数当然未必能达到理想情况,但是由“1/S”这个式子不难想到,加大S就能有效地减少判错的可能性。并且还能得出另一个结论:如果一个Hash函数的精确程度不够,只需要再计算一个与之不同的Hash函数,就可以有效地提高正确率!当然,在本题中,时间复杂度已经近似于(因为毕竟这个Hash函数不是理想的Hash函数)O(kN+N*(1/S)*M)了。S只要大小不比N/k小,后一项就不是时间复杂度的瓶颈了,也没有必要在这方面下太大功夫。反而,如果计算多个Hash函数,会显

10、著增加算法的常数。关于p和q的取值,由于可以理解成一个p进制的转换,再对q取余,应当在范围允许的情况下尽量避免冲突,建议:1、p不宜过小,不然很容易出错。2、q可以取一个质数,然后p选取一个能使对于所有1至p-2的i,pi mod q1。这个Hash函数不但可以这样滚动计算,也可以采用分段,分块,线段树等办法计算和维护,具体的一些办法可以参见后面的一些例题。例题2 Equal squares (Ural 1486)题目大意在一个NM的字符矩阵中找到两个相同的子正方形矩阵(可以相交),并使找到的两个子正方形矩阵的边长尽量大。算法分析有了例题1的经验,在面对本题的时候不难想到,二分查找正方形的边长

11、,然后使用一个Hash表来判断该边长是否可行。Hash函数与例题1的二维情况一致。时间复杂度是O(NMlog(N*M)的。但是不幸的是,本题时间限制很严,这样做的程序依然会超时。最后在使用一个看上去有点冒险的改动之后,终于通过了本题:直接检查是否产生了相同的Hash函数,如果存在相同的Hash函数,则认为该边长可行,否则即认为它不可行。这道题目就这样通过了,但是这样的改动是不是太冒险了一点呢?事实上,两个子正方形的内容不同而Hash值相同的可能性很小。所以,一般情况下我们可以认为,如果Hash值相同,则原内容相同。这里还有一个问题:Hash表存放了什么?如果存放的是Bool而不使用位压缩,则似

12、乎浪费了不少空间,而且Hash表不可能开到很大,因此两个不同的内容Hash到同一个位置还是有可能的,简单地比较“这个位置是否被Hash到”仍然不算保险,毕竟,一次查询有100万分之一的可能性出错的话,100万次查询出错概率就很可观了。这里介绍的一个小技巧是计算两个Hash值,一个用于确定Hash表中的位置,另一个则用于比较。这样,只有如果在Hash表的某个地址开始查找找到了一个Hash值与自己的Hash值一样的元素,才认为自己在Hash表中出现过。例题3不稳定匹配题目大意有两个串A和B,每次可以进行的操作有:INSERT i j:在A中第i个字符前插入j。DELETE i:删除A中第i个字符。

13、REVERSE i j:把A中i到j之间的内容反转。QUERY i j:求A从第i个字符开始,B从第j个字符开始能匹配的最大长度,即询问A从第i个字符开始的后缀与B从第j个字符开始的后缀的LCP长度。算法分析如果两个串不会变化,求LCP只需要求出A+B的后缀数组即可。但是本题的A串是不断变化的,而且由变化的方式可以看出,每次操作都会导致后缀数组发生很大的变化,因此我们应该另辟蹊径。对于一个k,不难判断两个串的LCP是否有至少k个字符:计算这两个串的前k个字符的Hash值,并且比较它们是否相等。如果相等,就几乎可以肯定地认为这两个串的LCP至少有k个字符,否则,它们的LCP长度肯定不到k。这样,

14、就可以通过二分查找来计算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

15、值:一个是正向的,一个是倒向的。除此之外的操作于维护部分和或者维护最大值类似。这样,插入,删除,反转操作是O(n0.5)的,而查询操作是O(n0.5logn)的。类似地,本题的另一种解法是在一棵splay树上维护Hash值。每次一个节点被旋转或以它为根的子树被修改时,则计算它的正向Hash值和反向Hash值,这样,就可以在O(1)时间你reverse一棵子树,通过split可以不难地把一棵子树在均摊O(logn)时间内反转。插入,删除操作显然也是均摊O(logn)的,而查询操作的均摊时间复杂度为O(log2n)。例题4 一类同构判定的问题问题1:比较两棵树是否相同。不难想到的算法是使用两个字符

16、串分别表示两棵树,但是如果使用Hash的话应该怎么做呢?可以使用一种类似树状递推的方法来计算Hash值:对于一个节点v,先求出它所有儿子节点的Hash值,并从小到大排序,记作H1,H2,HD。那么v的Hash值就可以计算为: 换句话说,就是从某个常数开始,每次乘以p,和一个元素异或,再除以q取余,再乘以p,和下一个元素异或,除以q取余一直进行到最后一个元素为止。最后把所得到的结果乘以b,再对q取余。之所以事先对儿子的Hash值进行排序,是因为仅仅儿子的顺序不同并不会导致树的不同,而后面如何Hash就比较随意了,即使不用这个Hash函数,只要使用一个效果足够好的Hash函数也是可以的。但是要注意

17、,诸如Rabin-Karp的那个Hash函数就不适合在这里应用,因为不难找到反例:考虑右边的树的儿子的顺序如图所示的情况(可以认为Hash函数的大小是随机分布的,因此有一半的可能性出现这种情况),由于叶子节点的Hash值必然相同(因为都是等价的),不妨记作l,然后递推关系是:则左边的树的Hash值为:右边为:不难看出他们是相等的,而两棵树是不相等的。类似地,前面的计算方法如果最后不乘以b,也是错的(可以分析树退化成线形的情况)。那么,既然直接比较Hash值肯定是有概率出错的,为什么还要指出哪些Hash函数适用,哪些不适用呢?有的错误是“偶然误差”,不改变Hash的计算方法,仅仅改变p等常数即可

18、消除,而有的则是“系统误差”,是这个Hash函数本身的不合理导致的,后一种情况应尽量避免出现。当然,在比赛的短短几个小时中未必能够判断这个Hash函数究竟合不合理,但是尽量选择在不确定p,q等可以改变的常数的情况下难以构造反例的Hash函数会是比较好的办法。现在,只需要比较两棵树的Hash值,即可分辨它们是否相同。时间复杂度为O(nlogn)。还有另一种Hash函数也能解决这个问题,而且对于解决下一个问题很有帮助:考虑把这棵树使用一个串表示出来(类似最小表示),然后计算那个串的Hash值。当然,如果真的像最小表示法那样把字符串弄出来,就有点得不偿失了:可以仅仅根据儿子的Hash值对儿子进行排序

19、,由于Rabin-Karp的Hash函数是可以处理两个串连接后的Hash函数值的,在确定了顺序并且知道了各个儿子的节点数(即子串的长度),不难确定当前节点的Hash值。换句话说,如果用c1,c2,cD表示当前节点v的儿子节点,s(x)表示根节点为x的子树所转化成的子串,则,这里g(v)是某个关于v的有一定识别能力的函数,比如取节点v的儿子数,或者某个关于节点v的Hash函数均可。而Rabin-Karp算法的Hash函数为因此有 可见,进行一次在树上的递推,则Hash值不难算出。问题2:比较两棵无根树是否相同。此时再使用最小表示的话,时间复杂度就很高了。但是,如果合理地使用Hash,时间复杂度依

20、然只有O(nlogn)。具体办法如下:对于每条边,计算一个Hash值,使之与所有满足的有关,这里和是两个概念,如图:可见,这样的递推计算不会引起循环,一定可以依照某个顺序进行。就用来表示“以a作为b的父亲节点时,b为根的子树的Hash值。”首先,任意选取一个节点r作为根节点,对于一个有序点对,满足,如果a比b离根节点近,则称是向下的,否则是向上的。显然,可以使用刚才问题1中给出的第二个方法在O(nlogn)时间内求出所有向下的对应的。ti现在考虑这个根节点r。显然所有都是向下的,因此所有都是已知的。现在就是要计算各个。对于r的某一个儿子节点t,如图所示:那么可以通过r的除了t以外其他的儿子节点

21、的Hash值计算出来。但是如果直接这样计算,时间复杂度在最坏情况下就是O(n2)的了。这里要用到树状递推的一个小技巧:由于所有都是已知的了,不妨事先对它们进行排序,然后考虑。由于这个Hash函数本质上还是对串的Hash,不妨把r所连接的各个点按排序,分别记作c1,c2,cD,记为所对应的那个串。可以在c1,c2,cD中二分查找到t的位置k,则可以表示成:略去不谈(同例一),和在排完序后都不难在O(n)时间预处理算出。因此,可以在O(log n)时间内算出,因为用到了二分查找。当一个节点v满足已经被计算出之后,就相当于对于所有与之相邻的点i,都是已经计算出的了。这个情况就和刚才关于根节点r的讨论

22、相同了,在此不再赘述。这样,第一遍dfs可以确定所有向下的,而第二遍dfs又可以确定所有向上的,这样,就计算出了。时间复杂度如何呢?第一遍复杂度显然是O(nlogn),而第二遍的时间复杂度:对于一个有D个儿子节点的节点,时间复杂度为O(DlogD) c1),Hash(x-c2),Hash(x-c3),x-color),f是某个Hash函数。在我的程序中,Hash函数为:f(a,b,c,d)=(129(129a xor b) xor c) xor 987654321) + d。顺便介绍一下,为了提高程序的运行效率,可以使用一些位操作的技巧,如a * 129 = (a shl 7) + a。例题5

23、 等价表达式(NOIP2005)题目大意给定一个没有化简的整式,然后n个整式,求出这n个整式里哪些与一开始给定的多项式相等。限制条件:n = 26,表达式长度不超过50,整式中只出现数字,a,+,-,*,(乘方运算),左右括号和空格。数字都小于10000,乘方运算中幂次都不超过10,+-*()都只能作运算符,且不会出现省略乘号的现象。算法分析常规的解法是把各个整式都展开并合并同类项,然后进行比较。这个算法当然是可以通过比赛时的测试数据的。但是这样的数据呢(显然这组数据是符合题目要求的)?(a+9999)99999999999999999999不难看出,这组数据展开以后得到的多项式根本无法存储下

24、来,也难以表示它后面各项的系数。面对这样的庞然大物,怎么比较两个都如此巨大的整式是否相同?显然,即便把这个式子计算出来都是极为困难的,更不要说去比较了。事实上,解决办法很简单:给a带一个或几个值,根据带值计算出的结果对某个大数字取余的余数(不取余的话上面那组“巨型”数据还是解决不了)是否相等,直接判定这两个整式是否相等。而代入一个值去求值,也可以看作是在本题应用了一个特殊的Hash函数对两个整式计算了Hash值,并通过比较Hash值来比较原式是否相等。当然,如果a取了若干个合适,可以证明出这个算法就变成一定正确的了。例题6 Guess the Number(OIBH Reminiscence

25、Programming Contest改编)题目大意给定一个S(最多500000位),求N,使得NN=S。但可能给定的S可能有一些位上的数字错了(不会出现增加或减少位数的情况),此时应输出-1。算法分析首先,对于比较小的S可以直接判断处理,而对于超过1010的S,我们可以确定一个可能的N(因为在N10时显然大于10,不可能位数相同),剩下的工作就是判断这个N满不满足NN=S。显然,算对数是不可取的,因为实数类型没有这么高的精度。同理,高精度计算也是难以实现的(即使是FFT也面临着时间复杂度的挑战)联系到前面的例题,可以想到,找到一个Hash函数f(x),通过比较f(NN)和f(S)是否相同即可

26、确定NN是否和S相等。同时,这个f(x)还要满足,如果x=ab,则可以方便地通过f(a)和f(b)来计算得到f(x),以便可以不把NN计算出来就直接比较。一个很简单但是很有效的Hash函数就是取余!即,f(x)=x mod p。如果x=ab,则f(x)= f(a) f(b) mod p。对于原题(S和NN最多相差一位),只要p不能被10整除,即可保证判断的准确性。而改编之后的题目呢(S可以修改任意位)?取若干个p,由于S的长度限制(只有500000)位,不超过130000以内质数的乘积。换句话说,在500000内随便取一个p,恰好能被|S-NN|整除的可能性只有大约1/4,如果多取一些,或者再

27、加大p的范围就可以几乎肯定判断是正确的了。【总结】Hash函数除了做为Hash表的辅助工具,在单独使用的时候可以实现几乎肯定地判断两个数据是否相同或等价。Hash函数的本质是对含有较大信息量的信息加以“概括”。因此在遇到需要频繁比较两个数据是否相同的操作的问题时,不妨考虑使用Hash函数作为解题的工具。有时使用Hash函数会牺牲一定的正确性,而且使用Hash函数的算法看上去也不够优美。但是灵活使用Hash函数,可以化难为易,解决一些原来难以解决的问题。也往往可以用较短的代码获得较高的效率。尽管有所舍弃,却也有所收获;正因为有所舍弃,才会有所收获。在选择算法的时候,总会遇到这样的矛盾,有失必有得,有得也必有失,选择恰当的适合题目也适合自己思维习惯的算法才是最好的。【参考文献】刘汝佳 黄亮算法艺术与信息学竞赛 清华大学出版社陈启峰NEW LCP2006年国家集训队作业数据结构清华大学出版社

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1