字串hash入門
簡單介紹一下字串hash
相信大家對於hash都不陌生
翻譯過來就是搞砸,亂搞的意思嘛
hash演算法廣泛應用於計算機的各類領域,像什麼md5,檔案效驗,磁力連結 等等都會用到hash演算法
在資訊學奧賽中,hash演算法主要應用於搜尋狀態判重,字串的比較等
hash的主要思想是:對於一個空間、時間需求較大的狀態,在一定錯誤率的基礎上進行狀態壓縮,降低其時間、空間的需求量
對於字串hash來說,就是把一串字串壓縮成一個hash值,方便我們進行資料的處理
接下來我們重點講一下字串hash的實現方法
實現方法
思想
在資訊學奧賽中,使用最廣泛的演算法叫做:BKDR Hash
它的核心思想是:
對於一個字串,選取恰當的進位制,將一個字串看做是一個大整數
(眾人:***,你這是要讓我們寫高精啊)
然後再對一個隨便什麼數取模就可以啦
當然這個“恰當的進位制”和“隨便什麼數”是有講究的
根據磚家的研究:
進位制一般選擇大於字串中的最大的字元且不含模數的值因子的數
比如說,如果你是對一串小寫字母做字串hash,那麼131這個進位制就是不錯的選擇
而“隨便什麼數”有三種方法
- 選擇兩個模數,判斷的時候只有兩個hash值都相同才算相同
- 選擇一個大質數,像11111111111111111111111或者212370440130137957
- 用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> 7ull#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 }
- 雙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
藍書(演算法競賽進階指南)刷題記錄——POJ3349 Snowflake Snow Snowflakes(最小表示法+字串hash)
題目:POJ3349. 題目大意:給定雪花可以用六元組 ( a 1