字符串hash
写给萌新的字符串hash算法,语言不严谨就算了,当然也欢迎dalao指点QAQ
\(hash\)是一种映射,在信息学中可以用于将一些不方便作为下标储存的结构当作一个数来存起来,方便\(O\)(1)的查找,可能不太好用,但是思维极其重要
字符串hash
模板:求两个字符串之间是否存在包含关系
KMP模板题a
例如\(bc\)和\(cbca\)这两个串,\(bc\)在\(cbca\)中出现过,它们存在包含关系,那我们是怎么看出来的呢
试着模拟一下寻找过程:\(bc\)串长度小一些,一定是用它来做模式串(字串)。从文本串(母串)中每次找一个长度为2的短串,看是不是和模式串一样的,一直找到结尾,在这里的\(cbca\)就分别有三个短串(\(cb\)、\(bc\)、\(ca\)),我们发现了第二次的\(bc\)和模式串一样,所以就判断出来找到了。
如果常规来做,时间为\(O\)(母串中短串个数*字串长度),约为\(O\)(mn)
首先枚举短串这一步没法优化了(要是有就不是\(hash\)了),所以主要优化找到短串后怎么\(O\)(1)的和模式串判断相不相同
求hash值
\(hash\)的特征就是不同的\(key\)(就是目标位置)对应的数据不同,所以将字符串转化为数字应该注意一一对应,避免哈希冲突(比如不同字符串对应了同一个值,但是你的程序还是会判断它们是同一个字符串)
一般的字符串\(hash\)值求法(终于到正题了)
给一个字符串
从左到右枚举字符串的每一位,每一个字母直接对应它的ASCII码(就变成\(int\)了),对应好了就把每一位加起来,就愉快的冲突了
直接相加会冲突,例如\(ab\)和\(ba\),第二串后来的那个\(a\)和第一串的前面的\(a\)虽然一个更老一个更年轻,但是它们的作用居然是一样的,这是不符合常识的(我是在说实话)
所以,为了使资质更老的\(a\)更显眼,可以在处理之后的那些后代的时候给它乘上一个数\(base\)显示它的不同。如果考虑到每一个字符后面都有后代的话,那么每处理一个后面的字符,前面的祖宗们就都会乘上一个数。容易看出,每一个位置都比它后面那个位置多乘了一次,这样就可以显示出各个位置的等级差距了2333
再结合之前的直接相加,就可以表示出来每一个不同的字符串了,即:
val ["abc"] = 'a'\(*\)base2+'b'$*$base1+'c'\(*\)base^0
那么对于一个母串,怎么提取它[ \(l\) , \(r\) ]中的\(hash\)值呢。我们已经知道了这个串从1到每个位置这一部分的\(hash\)值,这类似于前缀和,即\(hash\)[ \(r\) ]-\(hash\)[ \(l\)-1 ],但是由于对于\(r\)位置的\(hash\)[ \(r\) ],它前面一部分(即被它包含在内的\(hash\)[ \(l\) ]部分)被多乘了许多次\(base\),减的时候应该给\(hash\)[ \(l\) ]也乘上
(换个说法:求出\(hash\)[ \(l\)-1 ]之后,继续向后面走,每走一步都会\(hash\)[ \(l\)-1 ]乘上\(base\),一直求到\(hash\)[ \(r\) ]时已经乘了(\(r\)-\(l\)+1)个\(base\)了,实际上
hash[ r ]=hash[ l,r ]+hash[ l-1 ]\(*\)base^(r-l+1))
所以答案应该是(多乘了的次数即[ \(l\),\(r\) ]区间长度)
val[ l , r ]=hash[ r ]-hash[ l-1 ]\(*\)base^(r-l+1)
最后,因为乘的\(base\)一般很大,所以乘多了容易爆,要取模,为了避免麻烦,一般使用\(unsigned\) \(long\) \(long\)
Q:hash如何支持单点修改?
A:可以用线段树维护
要用线段树维护要资瓷区间合并->
hash=左子树hash*(base^右子树size)+右子树hash