寫給萌新的字符串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'\(*\)base^2+'b'\(*\)base^1+'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