hash入門


如果你已經確保自己的hash技巧已經入門,那么請左轉這篇博文

首先介紹一下hash?

事實上是一種叫做蛤絲的病毒

以下講到的hash都是OI中最常用到的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;
}

這道題也是可以打字典樹的,也是裸的做法,讀者也可以嘗試一下,因為這里是講hash的所以就不放字典樹的代碼了


免責聲明!

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



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