字符串哈希


 

http://acm.uestc.edu.cn/#/problem/show/1092

韓爺的夢

Time Limit: 200/100MS (Java/Others)     Memory Limit: 1300/1300KB (Java/Others)
 

一天,韓爺去百度面試,面試官給了他這么一個問題。

給你2萬個字符串,每個字符串長度都是100,然后把2萬個字符串丟入一個 set< string >g 中,問最終set里含有多少個元素?
g 是一個用來存儲字符串、具有去重功能的容器,即相同字符串在 g 中只能保留一個。
兩個字符串相等,當且僅當,長度一樣且對應位置的字符都一樣。

韓爺前晚沒睡好,隨手寫了一個程序交給面試官,然后就gg了。

#include<iostream> #include<string> #include<set> using namespace std; string s; set<string>g; int main(){ for(int k=1;k<=20000;k++){ cin>>s; g.insert(s); } cout<<g.size()<<endl; return 0; }

韓爺醒來之后,發現這只是一個夢(還好只是個夢)。他回憶起夢中的面試官給他的內存限制和時間限制非常低,這么做肯定過不了,那么,現在你不在夢中,你能解決這個問題么?

Input

單case

每個case有且只有2萬行,每一行包含一個字符串,每行字符串的長度都為100 (樣例除外)

字符集:大寫英文字母(A-Z),小寫英文字母(a-z),數字(0-9)

Output

輸出一個整數,表示最終set里含有多少個元素。

Sample input and output

Sample Input Sample Output
aaAa
aaAa
bbbb
1234
bbbb
bbbb
ee09
4

Hint

樣例只是樣例,不在test中

注意時間限制和內存限制非常低

 

 

思路:這道題目難點在於時間與內存限制很苛刻,一般的方法不能奏效,這里只能采用hash。即把每個字符串hash為一個數字,對數字進行比對,題目就ac了。還有個問題就是,hash函數的選取。我第一次選的hash函數就產生了沖突,這個可以多次選擇進行測試,也可以直接采用更復雜的hash函數。我偷懶了下,選的是前者的方法,第二發就ac了。

這里說下關於hash的知識:

求一個字符串的hash值:

•現在我們希望找到一個hash函數,使得每一個字符串都能夠映射到一個整數上
•比如hash[i]=(hash[i-1]*p+idx(s[i]))%mod
•字符串:abc,bbc,aba,aadaabac
•字符串下標從0開始
•先把a映射為1,b映射為2,c->3,d->4,即idx(a)=1, idx(b)=2, idx(c)=3,idx(d)=4;
•好!開始對字符串進行hash

假設我們取p=13 ,mod=101

先把abc映射為一個整數

hash[0]=1,表示 a 映射為1

hash[1]=(hash[0]*p+idx(b))%mod=15,表示 ab 映射為 15

hash[2]=(hash[1]*p+idx(c))%mod=97

這樣,我們就把 abc 映射為 97 這個數字了。

•用同樣的方法,我們可以把bbc,aba,aadaabac都映射到一個整數
•用同樣的hash函數,得到如下結果
• abc  ->  97
• bbc  ->  64
• aba  ->  95
• aadaabac  ->  35
•那么,我們發現,這是一個字符串到整數的映射
•這樣子,我們就可以記錄下每個字符串對應的整數,當下一次出現了一個已經出現的字符串時,查詢整數是否出現過,就可以知道 字符串是否重復出現。
•現在要判斷兩個字符串是否一致,怎么辦呢?直接用它們的hash值判斷即可,若hash值一致,則認為字符串一致;若hash值不一致,則認為是不同的字符串。
•我們要判斷兩個字符串是否一致,沒有那么麻煩,直接先判斷長度是否一致,然后再判斷每個對應的字符是否一致即可。
•但,如果要判斷多個字符串里有多少個不同的字符串,怎么辦呢?
•兩兩字符串都進行比較?時間復雜度太高
•把每個字符串hash成一個整數,然后把所有整數進行一個去重操作,即可知道答案了。
當遇到沖突時,我們可以想辦法調整p和mod,使得沖突概率減小之又小。我們一般認為p和mod一般取素數,p取一個較大的素數即可(6位到8位),mod取一個大素數,比如1e9+7,或者1e9+9。
 
如何求一個子串的hash值?
•在之前,我們求出了hash[i],表示第i個前綴的hash值。現在怎么求出每個子串的

   hash值呢?

•我們看下hash的公式:
• hash[i]=(hash[i-1]*p+idx(s[i]))%mod
•這表示第 i 個前綴的hash值,是一個hash的前綴和。
•hash[i]=(hash[i-1]*p+idx(s[i]))%p;
•那么,我要求S[l…r]這個子串的hash值
• hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod(假設字符串下標從1開始)
•但注意下取模時候的問題!
•hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod
• hash[l..r]是不是可能有負數?
•怎么辦呢?當得到的hash[l..r]<0的時候,hash[l..r]+=mod,就好啦。
•這樣就可以保證每個子串的hash值在[0, mod-1]的范圍內,准確地用hash值來處理字符串
 
常用的幾個字符串hash法
•1. unsigned long long hash[N];
     hash[i]=hash[i-1]*p(自動取模)
解釋:

unsigned long long hash[N];

定義一個unsigned long long類型的變量,它的范圍是在[0, 2^64) 內,這就相當於,當數超不過2^64-1后,它會溢出!這就相當於一個數模2^64的過程。

那么hash函數可以理解為:

       hash[i]=(hash[i-1]*p)%(2^64)

P取一個大素數,一般習慣取1e9+7或1e9+9

安全指數:三星(所以並不是很安全)

 

•2. hash[i]=(hash[i-1]*p+idx(s[i]))%mod
解釋:

這個之前已經提到過了。   

 hash[i]=(hash[i-1]*p+idx(s[i]))%mod

p取一個6到8位的素數,mod取一個大素數,一般取1e9+7或1e9+9
安全指數:四星 (還可以)
 
•3. 雙hash

     hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

     hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

     pair<hash1,hash2>表示一個字符串!

解釋:

double hash
即取兩個mod值,mod1和mod2

 hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

 hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

 mod1一般取1e9+7,mod2一般取1e9+9為什么這么取?

1000000007和1000000009是一對孿生素數,取它們,沖突的概率極低!

安全指數:五星!(非常穩!)
 
小結:
•可以這么說,hash某種程度上就是亂搞,把hash函數弄的越沒有規律越好,使得沖突的概率小到 大部分數據都卡不掉。
•如果你開心,你想triple hash,ultra hash,rampage hash… 都沒有問題!

 但請注意,hash的維度越高,耗時越高,耗內存越大!一般情況下,single hash可以被hack掉,但double hash極難被hack掉, 用double hash足以解決問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM