1. 程式人生 > >字串hash入門

字串hash入門

簡單介紹一下字串hash

相信大家對於hash都不陌生

翻譯過來就是搞砸,亂搞的意思嘛

hash演算法廣泛應用於計算機的各類領域,像什麼md5,檔案效驗,磁力連結 等等都會用到hash演算法

在資訊學奧賽中,hash演算法主要應用於搜尋狀態判重,字串的比較等

hash的主要思想是:對於一個空間、時間需求較大的狀態,在一定錯誤率的基礎上進行狀態壓縮,降低其時間、空間的需求量

對於字串hash來說,就是把一串字串壓縮成一個hash值,方便我們進行資料的處理

接下來我們重點講一下字串hash的實現方法

實現方法

思想

在資訊學奧賽中,使用最廣泛的演算法叫做:BKDR Hash

它的核心思想是:

對於一個字串,選取恰當的進位制,將一個字串看做是一個大整數

(眾人:***,你這是要讓我們寫高精啊)

然後再對一個隨便什麼數取模就可以啦

當然這個“恰當的進位制”和“隨便什麼數”是有講究的

根據磚家的研究:

進位制一般選擇大於字串中的最大的字元且不含模數的值因子的數

比如說,如果你是對一串小寫字母做字串hash,那麼131這個進位制就是不錯的選擇

而“隨便什麼數”有三種方法

  1. 選擇兩個模數,判斷的時候只有兩個hash值都相同才算相同
  2. 選擇一個大質數,像11111111111111111111111或者212370440130137957
  3. 用unsigned long long 自然溢位

注意:儘量不要只用一個較小的質數,根據生日悖論,很容易被卡

程式碼

程式碼實現比較簡單

https://www.luogu.org/problemnew/show/3370

  • unsigned long long 自然溢位
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<cstring>
 6 #include<algorithm>
 7
#include<map> 8 #define lli long long int 9 using namespace std; 10 const int MAXN=100001; 11 int seed=27; 12 void read(int &n) 13 { 14 char c='+';int x=0;bool flag=0; 15 while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;} 16 while(c>='0'&&c<='9')x=x*10+c-48,c=getchar(); 17 n=flag==1?-x:x; 18 } 19 char a[MAXN]; 20 map<long long ,bool>happen; 21 int tot=0; 22 int main() 23 { 24 int n; 25 read(n); 26 for(int i=1;i<=n;i++) 27 { 28 unsigned long long base=1; 29 scanf("%s",a); 30 for(int j=0;j<strlen(a);j++) 31 base=base*seed+(a[j]); 32 if(!happen[base]) 33 happen[base]=1,tot++; 34 } 35 printf("%d",tot); 36 return 0; 37 }
ull
  • 雙hash
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ull unsigned long long 
 5 using namespace std;
 6 const int MAXN=2e7+10;
 7 const int mod1=19260817;
 8 const int mod2=19660813;
 9 const int seed=233;
10 const int seed2=133;
11 int n;
12 struct node
13 {
14     char s[1501];
15     int h1,h2,l;
16 }a[10001];
17 ull gethash1(char *s,int l)
18 {
19     ull ans=0;
20     for(int i=1;i<=l;i++)
21         ans=(ans*seed+(ull)s[i])%mod1;
22     return ans;
23         
24 }
25 ull gethash2(char *s,int l)
26 {
27     ull ans=0;
28     for(int i=1;i<=l;i++)
29         ans=(ans*seed2+(ull)s[i])%mod2;
30     return ans;
31 }
32 int hash1[MAXN],hash2[MAXN];
33 int main()
34 {
35     scanf("%d",&n);
36     for(int i=1;i<=n;i++)
37     {
38         scanf("%s",a[i].s+1);
39         a[i].l=strlen(a[i].s+1);
40     }
41     for(int i=1;i<=n;i++)
42     {
43         a[i].h1=gethash1(a[i].s,a[i].l)%mod1;
44         a[i].h2=gethash2(a[i].s,a[i].l)%mod2;
45     }
46     int ans=0;
47     for(int i=1;i<=n;i++)
48         if(hash1[a[i].h1]==0||hash2[a[i].h2]==0)
49             hash1[a[i].h1]=1,hash2[a[i].h2]=1,ans++;
50     printf("%d",ans);
51     return 0;
52 }
雙hash
  • 大質數hash
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<map>
 8 #define lli long long int 
 9 using namespace std;
10 const int MAXN=100001;
11 const long long int mod=212370440130137957;
12 int seed=27;
13 void read(int &n)
14 {
15     char c='+';int x=0;bool flag=0;
16     while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;}
17     while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
18     n=flag==1?-x:x;
19 }
20 char a[MAXN];
21 map<int,bool>happen;
22 int tot=0;
23 int main()
24 {
25     int n;
26     read(n);
27     for(int i=1;i<=n;i++)
28     {
29         long long base=1;
30         scanf("%s",a);
31         int la=strlen(a);
32         for(int j=0;j<la;j++)
33             base=(base*seed+(a[j]) )%mod;
34         if(!happen[base])
35             happen[base]=1,tot++;
36     }
37     printf("%d",tot);
38     return 0;
39 }
大質數

特別注意:在迴圈的時候,不要寫: for(int j=0;j<strlen(a);j++) !!!!因為strlen的複雜度是$O(n^2)$,資料稍微一大你就會被卡T

正確的姿勢是把長度記錄下來 int la=strlen(a); for(int j=0;j<la;j++) 

常用技巧:區間hash值

在進行字串hash的時候,我們經常會用到某一段的hash值

這時候怎麼辦呢?

假設我們已經得到了$hash[1-r]$

那麼$hash[l-r]=hash[r]-seed^{r-l+1}*hash[l]$(seed表示進位制)

舉個例子

$hash[1]=a1$

$hash[2]=a1*base+a_2$

$hash[3]=(a_1*seed+a_2)*seed+a_3=a_1*seed^2+a_2*seed+a_3$

$hash[4]=[(a[1*seed+a_2)*seed+a_3]*seed+a_4=a1*seed^3+a2*seed^2+a_3*seed+a_4$

$hash[3-4]=hash[4]-(4-3+1)^2*hash[2]=a_3*seed+a_4$

很明顯可以看出這個公式是對的

最好自己手寫一下,這樣才能體會到其中的奧妙

經典題目

必做練手題,程式碼已經在上面給出了

這道題可以用字串hash水過

http://www.cnblogs.com/zwfymqz/p/7793347.html

這道題理論上是可以用字串hash做的

但是我只水到90分,各位神犇可以嘗試一下

http://www.cnblogs.com/zwfymqz/p/7355159.html

 略有難度

http://www.cnblogs.com/zwfymqz/p/7349658.html

其他經典應用

以下內容來自遠航之曲大神,在此表示衷心的感謝

1、kmp

問題:給兩個字串S1,S2,求S2是否是S1的子串,並求S2在S1中出現的次數

把S2 Hash出來,在S1裡找所有長度為$|S2|$的子串,Hash比較。效率$O(|S1|)$

2、AC自動機

問題:給N個單詞串,和一個文章串,求每個單詞串是否是文章串的子串,並求每個單詞在文章中出現的次數。

把每一個單詞hash成整數,再把文章的每一個子串hash成整數,接下來只需要進行整數上的查詢即可。

複雜度:$O(|A|2+|S|)$

用AC自動機可以做到$O(|A|+|S|)$的複雜度,|S|是單詞串總長,$|A|$是文章長度

3、字尾陣列

問題:給兩個字串S1,S2,求它們的最長公共子串的長度。

將S1的每一個子串都hash成一個整數,將S2的每一個子串都hash成一個整數

兩堆整數,相同的配對,並且找到所表示的字串長度最大的即可。

複雜度:$O(|S1|2+|S2|2)$

用字尾陣列可以優化到$O(|S|log|S|)$

4、馬拉車

問題:給一個字串S,求S的最長迴文子串。

先求子串長度位奇數的,再求偶數的。列舉迴文子串的中心位置,然後二分子串的長度,直到找到一個該位置的最長迴文子串,不斷維護長度最大值即可。

複雜度:$O(|S|log|S|)$

用manacher可以做到$O(|S|)$的複雜度

5、擴充套件kmp

問題:給一個字串S,求S的每個字尾與S的最長公共字首

列舉每一個字尾的起始位置,二分長度,求出每個字尾與S的最長公共字首。

複雜度:$O(|S|log|S|)$

用extend-kmp可以做到$O(|S|)$的複雜度

總結

字串hash是一個非常優秀的演算法。

希望大家能夠熟練的掌握&&運用

說不定它可以在你寫不出正解的時候幫你得很多分呢?

相關推薦

hash字串hash入門

一.hash的引入. 考慮一個問題,給定n( 1 ≤ n

字串hash入門

簡單介紹一下字串hash 相信大家對於hash都不陌生 翻譯過來就是搞砸,亂搞的意思嘛 hash演算法廣泛應用於計算機的各類領域,像什麼md5,檔案效驗,磁力連結 等等都會用到hash演算法 在資訊學奧賽中,hash演算法主要應用於搜尋狀態判重,字串的比較等 hash的主要思想是:對於一

【演算法學習】字串Hash入門

字串Hash入門 字串Hash可以通俗的理解為,把一個字串轉換為一個整數。 如果我們通過某種方法,將字串轉換為一個整數,就可以便的確定某個字串是否重複出現過,這是最簡單的字串Hash應用情景了。 當然也不難想到,如果有不同的兩個字元串同時Hash

(通俗易懂小白入門字串Hash+map判重——暴力且優雅

字串Hash 今天我們要講解的是用於處理字串匹配查重的一個演算法,當我們處理一些問題如給出10000個字串輸出其中不同的個數,或者給一個長度100000的字串,找出其中相同的字串有多少個(這樣描述有點不清楚但是大致的意思就是當字串長度很長,而且涉及到多個字串之間反覆比較時,由於比較的次數多,字串長,很容易就超

ural1989 單點更新+字串hash

正解是雙雜湊,不過一次雜湊也能解決。。 然後某個數字就對應一個字串,雖然有些不同串對應同一個數字,但是概率非常小,可以忽略不計。從左到右、從右到左進行兩次hash,如果是迴文串,那麼對應的整數必定存在某種關係(可以理解成相等),對於更新操作,就是單點更新。 #include<iostream&

Power Strings---字串hash

問題 C: 【雜湊和雜湊表】Power Strings 時間限制: 1 Sec  記憶體限制: 128 MB 提交: 38  解決: 18 [提交] [狀態] [討論版] [命題人:外部匯入] 題目描述 Gi

luogu4407 [JSOI2009]電子字典 字串hash + hash

暴力列舉,然後\(hash\)表判斷 複雜度\(O(26 * 20 * n)\) 具體而言 對於操作1:暴力列舉刪除 對於操作2:暴力新增,注意新增不要重複 對於操作3:暴力替換,同樣的注意不要重複 #include <cstdio> #include <cstr

洛谷P1032 字串變換 【kmp,字串hash

大意 給定轉換規則,求最小步數 思路 其實可以用AC自動機 這道題是問我們最小步數,因為其分支不大(≤7\leq7≤7)容易想到專門處理最優化問題的bfsbfsbfs演算法 在bfsbfsbfs的匹配中,本人採用的是用字元陣列模擬字串中的運算,建立新的“Str

985F Isomorphic Strings(字串Hash雜湊)

F. Isomorphic Strings time limit per test 3 seconds memory limit per test 256 megabytes input standard input output standard outpu

【資料結構】【線段樹】【字串Hash】2018國慶三校聯考D4T3

題意: 分析: 題解見標籤 (不過這題有非正解方法可以卡過去。。我程式碼附在下面) 正解: #include<cstdio> #include<cstring> #inclu

nssl1211-好文章【字串hash,map】

正題 題目大意 求長度為n個一個字串長度為m不同的子串個數 解題思路 用字串hash判斷字串是否相同,然後時間複雜度O(n2)O(n^2)O(n2),然後我們因為自然溢位所以不能開桶,那就開map。

URAL - 1989 Subpalindromes (字串hash + 線段樹區間合併)

題目連結:http://acm.timus.ru/problem.aspx?space=1&num=1989 題目大意:給出一個長度為n的字串S,接下來進行n次操作。操作分為修改和查詢兩種,每次修改操作給出一個整數 i 和一個字元c,表示將第 i 位的字元變成字元c;每次查詢操作給出兩個

字串hash(scu4438)

Censor frog is now a editor to censor so-called sensitive words (敏感詞). She has a long text pp. Her job is relatively simple -- just to f

codeforces #336 E. Marbles (字串hash或者kmp匹配)

題意:有兩個通道,在每個通道的起點分別放一個小球,然後問是否存在一系列指令使得兩個小球最後都在終點。 若小球碰壁,則保持原來的位置不變。 Hash程式碼: #include <bits/s

CH 1401 兔子與兔子 字串hash

描述 很久很久以前,森林裡住著一群兔子。有一天,兔子們想要研究自己的 DNA 序列。我們首先選取一個好長好長的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 個小寫英文字母),然後我們每次選擇兩個區間,詢問如果用兩個區間裡的 DNA 序列分別生產出來兩隻兔子,這

upc 兔子與兔子(字串Hash)

問題 I: 兔子與兔子 時間限制: 1 Sec  記憶體限制: 128 MB 提交: 15  解決: 9 [提交] [狀態] [討論版] [命題人:admin] 題目描述 很久很久以前,森林裡住著一群兔子。有

字串hash + 二分答案

// Memory Time // 1347k 0MS // by : Snarl_jsb // 2014-10-04-21.16 #include<algorithm> #include<cstdio> #include<cstring> #include&

hdu-1800(字串hash

題目連結:傳送門 思路: 就是找最多多少個掃帚,每個掃帚上有連續遞增的序列,就是找一個最多重複數字的重複次數。 由於是30位,每次用char*型別,然後用hash對映一下,排序找最多就行了。 注意: (1)num最小也是1。 (2)注意前導零。 #include<io

poj2774(字尾陣列||字串hash

求兩個串的最長公共子串 將兩個串連起來,求字尾陣列,sa中相鄰兩個字尾如果不屬於同一個模版,則用這個height更新答案,他們的最長公共字首更新答案 #include<cstdio> #include<cstring> #include<cs