hash詳解
首先介紹一下hash?
事實上是一種叫做蛤絲的病毒
hash的做法:
首先設一個進制數base,並設一個模數mod
而哈希其實就是把一個數轉化為一個值,這個值是base進制的,儲存在哈希表中,註意一下在存入的時候取模一下即可
比如說現在有一個字符串orzc
枚舉這個字符串的每一位,與base相乘得到ans,然後mod一下,就得到orzc的哈希值
但是哈希有一個很大的弊端:
哈希沖突
什麽是哈希沖突呢?
就比如說orzc的哈希值是233,而orzhjw的哈希值也是233
那麽我們在查詢的時候代碼會認為這兩個字符串是相同的,但顯然這兩個字符串是不同的
減少哈希沖突的方法很多
自然溢出法,雙哈希之類的
看一道例題理解一下
洛谷P3370 【模板】字符串哈希
題目描述
如題,給定N個字符串(第i個字符串長度為Mi,字符串內包含數字、大小寫字母,大小寫敏感),請求出N個字符串中共有多少個不同的字符串。
友情提醒:如果真的想好好練習哈希的話,請自覺,否則請右轉PJ試煉場:)
輸入輸出格式
輸入格式:
第一行包含一個整數N,為字符串的個數。
接下來N行每行包含一個字符串,為所提供的字符串。
輸出格式:
輸出包含一行,包含一個整數,為不同的字符串個數。
輸入輸出樣例
輸入樣例#1: 復制5
abc
aaaa
abc
abcc
12345
輸出樣例#1: 復制
4
說明
時空限制:1000ms,128M
數據規模:
對於30%的數據:N<=10,Mi≈6,Mmax<=15;
對於70%的數據:N<=1000,Mi≈100,Mmax<=150
對於100%的數據:N<=10000,Mi≈1000,Mmax<=1500
樣例說明:
樣例中第一個字符串(abc)和第三個字符串(abc)是一樣的,所以所提供字符串的集合為{aaaa,abc,abcc,12345},故共計4個不同的字符串。
Tip: 感興趣的話,你們可以先看一看以下三題:
BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097
BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098
BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099
如果你仔細研究過了(或者至少仔細看過AC人數的話),我想你一定會明白字符串哈希的正確姿勢的^_^
事實上如果理解了剛剛講的hash的原理的話,這道題就很水了,因為本來就是模板題
用一段hash的代碼再來鞏固一下剛才的知識
#define base 233 #define inf 1<<30 ull mod=inf; //定義一個大數(最好是質數)作為模數,這裏用的是1<<30 //定義一個base進制,這裏是233 il ull hash(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod; } return ans; //枚舉該字符串的每一位,與base相乘,轉化為base進制,加(ull)是為了防止爆棧搞出一個負數,(ull)是無符號的,但其實加了一個ull是可以不用mod的,加個mod更保險 //然而加了mod會很玄學,莫名比不加mod慢了300多ms }
因為懶就沒有去找一個大質數來當mod,用了1<<30代替,但是最好還是找一個大質數當mod(搜索一下生日悖論?大概就會明白原因了)
最後貼一下剛剛的例題的兩種解法:
解法1:單hash/自然溢出法
這裏就當一種解法來說吧
因為代碼差異不大
這道題的話單hash mod開大質數是可以過的,但是在大多數難一些的題目裏面是會被卡掉的#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define mt(x,y) memset(x,y,sizeof(x))
#define il inline
#define ull unsigned long long
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il ll swap(ll x,ll y){ll t=x;x=y;y=t;}
il void read(ll &x){
x=0;ll f=1;char c=getchar();
while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-f;c=getchar();}
while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
x*=f;
}
using namespace std;
#define N 10001
#define base 233
ull mod=212370440130137957ll;
ll f[N],n;
char a[N];
//ull hash(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=((base*ans+(ull)s[i])+mod)%mod; } return ans; }
//這個是單hash+大質數mod,也是可以過的,但是會比較慢
ull hash(char s[]){//自然溢出
ull ans=0,len=strlen(s);
for(ll i=0;i<len;i++){
ans=base*ans+(ull)s[i];
//這裏不使用mod讓它自然溢出,定義為ull的數在超過2^32的時候會自然溢出
//如果把這個換成上面的hash就會400ms+
//所以說自然溢出大法好
}
return ans;
}
int main(){
read(n);
for(ll i=1;i<=n;i++){
scanf("%s",a);
f[i]=hash(a);
}
sort(f+1,f+n+1);ll ans=1;
for(ll i=1;i<n;i++){
if(f[i]!=f[i+1])ans++;
}
printf("%d\n",ans);
return 0;
}
解法2:雙hash
其實就是用兩個不同的mod來算hash,哈希沖突的概率是降低了很多,不過常數大,容易被卡,這道題要700ms+
本人還是更推薦自然溢出法
#include <cstdio> #include <cstring> #include <algorithm> #define ll int #define inf 1<<30 #define mt(x,y) memset(x,y,sizeof(x)) #define il inline #define ull unsigned long long il ll max(ll x,ll y){return x>y?x:y;} il ll min(ll x,ll y){return x<y?x:y;} il ll abs(ll x){return x>0?x:-x;} il ll swap(ll x,ll y){ll t=x;x=y;y=t;} il void read(ll &x){ x=0;ll f=1;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-f;c=getchar();} while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();} x*=f; } using namespace std; #define N 10001 #define base 233 ull mod1=212370440130137957ll; ull mod2=inf; ll n; char a[N]; struct node{ll x,y;}f[N]; il ull hash1(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod1; } return ans; } il ull hash2(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod2; } return ans; } il bool cmp1(node a,node b){return a.x<b.x;} il bool cmp2(node a,node b){return a.y<b.y;} int main(){ read(n); for(ll i=1;i<=n;i++){ scanf("%s",a); f[i].x=hash1(a); f[i].y=hash2(a); } sort(f+1,f+n+1,cmp1);sort(f+1,f+n+1,cmp2); ll ans=1; for(ll i=1;i<n;i++){ if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y)ans++; } printf("%d\n",ans); return 0; }
這道題也是可以打字典樹的,rank1的dalao就是用的字典樹,不過就不介紹了,其他的題解裏面也有,況且既然是哈希模板,就好好打哈希咯233
我是不會承認因為我懶而且不是很懂字典樹才不放字典樹做法的
hash詳解