1. 程式人生 > >字串系列(一)——偉大的字串Hash

字串系列(一)——偉大的字串Hash

在刷省選時,我們時常會遇到一些字串的題目。也有許多的演算法供我們選擇,如:

KMP、拓展KMP、最小表示法、Manacher、Trie、字尾陣列、字尾自動機、AC自動機(樹上KMP)等等等,日後我會逐一填坑。今天先來介紹一個最暴力且用途最大的——字串Hash。

首先,要想學好Hash,必須要明白一件事:兩個元素若全等,其雜湊值必定也相等;但雜湊值相等,兩個元素未必全等(雜湊值相等是兩個元素全等的必要不充分條件)。

看了這句話我想大家都明白,Hash時要儘量使衝突概率趨近於0,即,儘量的滿足其充分性。下面介紹一種Hash方法。

先取一固定值P(prime的首字母,可以看出這個P取質數最好,而且一般用大質數),把字串看作一個P進位制的數。(我想這個並不難理解,因為16進制中也包含著一些字母)然後取出另一固定值M,求出該P進位制數對M取餘的結果,即為字串雜湊值。M我們一般取2的64次方,但實際操作時呢我們不需要對M取模(取模是個效率極低的運算)。我們用unsigned long long型別儲存雜湊值,讓其自然溢位就好,因為此型別是不會出現負數的(無符號位)。P的話我參考了李煜東前輩的給出的值——131或13331,此時衝突概率極低。

如果你還是不放心,那可以同時做好多組P與M,判斷時多判斷幾次,如果所有的雜湊值都相等,再得出結論。

在處理子串問題時,我們一般採用字首和去維護雜湊值(當然,你可以使用樹狀陣列啊,平衡樹啊,去動態維護整個序列,比如bzoj1014,後續我會給出題解)。這裡介紹一個字首和的方法。首先按照進製法則預處理出每一個字首的雜湊值,程式碼如下:

for(int i = 1; i <= n; ++i) hash[i] = hash[i - 1] * P + str[i] - 'a';  

如果我們需要取出某段子串(str[l~r])的雜湊值,程式碼如下:

inline ull getsub(int l, int r) {return hash[r] - hash[l - 1] * bin[r - l + 1];}  

也就是:hash[l~r] = hash[1~r] - hash[l - 1] * pow(P, r - l + 1);

這個bin陣列也是ull型別的,bin[i]表示P的i次方64位自然溢位的結果,也就是:

hash[l~r] = hash[1~r] - hash[1~l - 1] * pow(P, r - l + 1); 

為了效率,我們提前用O(n)的時間預處理了bin。我第一次做字串Hash時(poj3974),自然是沒有暴力算pow。我使用了快速冪【捂臉】,一個logn直接導致超時(可怕的logn)。

字串Hash一般是伴隨著二分出現的,就比如poj3974,我們要得到最大值,自然可以二分。

就寫到這裡了,後續我還會發一些字串Hash的神奇應用的。(貌似這篇blog裡挖了好多坑等我去填)