最近打暑假多校,發現有許多字串演算法自己有所遺忘,今天就藉著補題在這開乙個坑,把那幾個基礎的字串演算法總結複習一下,順便寫幾個模板,供今後使用。這篇部落格主要就是總結一下字串hash,並提一下例題。
我們學習乙個演算法,肯定要先知道它要解決的是什麼問題。字串hash,就是解決字串匹配問題的良藥,即尋找長度為 n 的主串 s 中的匹配串 t(長度為 m)出現的位置或次數問題。
對於上述問題,樸素的想法是列舉 s 所有起始位置,再直接檢查是否匹配,我們可以不使用 o(m) 的直接比較字串的方法,而是比較長度為 m 的主串 s 的子串的雜湊值與 t 的雜湊值是否相等,這就是雜湊演算法解決這類問題的原理,這個原理稱為字串 hash。
大多數字串 hash 問題可以用 kmp 求解,但如果是從主串中每次選出兩個子串判斷是否匹配的問題,還是要用字串 hash 求解(不必糾結,遇到自然懂)。
如果我們用 o(m) 的時間計算長度為 m 的字串的雜湊值,則總的時間複雜度並沒有改觀,這裡就需要乙個叫做滾動雜湊的優化技巧。
我們選取兩個合適的互質常數 b 和 h(b1c
2…cm ,那麼我們定義雜湊函式:h(c)=(c1bm-1 + c2bm-2 + … + cmb0) mod h。
正常的數字是十進位制的,這裡b 是基數,相當於把字串看作是 b 進製數。
這一過程是遞推計算的,設 h(c,k) 為前 k 個字元構成的字串的雜湊值,則:(以下均不考慮取模的情況)。
h(c,k+1) = h(c,k)*b + ck+1
如字串 c=「acda」(為方便處理,我們令"a"表示 1,"b"表示 2,以此類推),則:
h(c,1) = 1
h(c,2) = 1*b + 3
h(c,3) = 1*b
2 + 3*b + 4
h(c,4) = 1*b
3 + 3*b
2 + 4*b + 1
**實現如下
h[0]
=0;for
(int i=
1;i<=
4;i++
) h[i]
=h[i-1]
*b+(c[i]
-'a'+1
);
通常,題目要求的是判斷主串的一段字元與另乙個匹配串是否匹配,即判斷字串 c=c1c2…cm 從位置 k+1 開始的長度為 n 的子串 c』=ck+1ck+2…ck+n 的雜湊值與另一匹配串 s=s1s2…sn 的雜湊值是否相等,則:
h(c』) = h(c,k+n) - h(c,k)*bn
於是我們只要預處理求得 b^n ,就能在 o(1)時間內得到任意字串的子串雜湊值,從而完成字串匹配,那麼上述字串匹配問題的演算法時間複雜度就為 o(n+m)。
**實現如下
for
(int i=
0;i<=m-n;
++i)
如字串 c=「acda」,s=「cd」,當 k=1, n=2 時:
h(c』) = h(c,1+2) - h(c,1)*b2
= (1*b2+3*b+4) - (1*b2)
= 3*b + 4
h(s) = 3*b + 4
因此子串 c』 與匹配串 s 匹配。
在實現演算法時,可以利用 32 位或 64 位無符號整數計算雜湊值,此時 h=232 或 h=264,通過自然溢位省去求模運算。(因為無符號整數,大於最大值後會以最大值+1為模數取模)
注1:眾所周知,hash演算法有時會產生衝突,但是在一般比賽中用字串hash產生衝突的概率是很小的,如果發現錯了,可以換個基數或模數,或者採用「雙雜湊」來避免衝突。
注2:我們在預處理 b^n 時,要根據題目來選擇預處理方式(雖然一般不會卡這個),如果只有一組輸入,匹配串長度固定,那麼利用快速冪求即可。如果有多組輸入,每組匹配串長度皆不同,遞推更好一些。
遞推**
pow[0]
=1;for
(int i=
1;i<=
10002
;i++
)//預處理base^n
pow[i]
=pow[i-1]
*base;
oulipo(poj3461)
題目大意
給出兩個串 s
1,s2(只有大寫字母),求 s
1 在 s
2 **現了多少次。例如 s
1=「aba」,s
2=「ababa」,答案為 2。輸入 t 組資料,對每組資料輸出結果。每組資料保證 strlen(s
1) <= 10
4,strlen(s
2) <= 10
6。 解題思路
將匹配串 s
1 的雜湊值求出來,再將母串 s
2 的雜湊值求出來,根據 h(c』) = h(c,k+n) - h(c,k)*b
n求出與匹配串長度相等的母串子串的雜湊值,與匹配串 s
1 的雜湊值比較,如果相等,答案+1。
**實現
/**
快速冪預處理,取模
跑了 813ms
*/#include
#include
#include
#include
#include
using
namespace std;
const
int maxn=
1e6+5;
const
int maxm=
1e4+5;
const
int base=29;
const
long
long mod=
1e7+5;
int n;
char w[maxm]
,t[maxn]
;long
long
quickpow
(long
long a,
int b)
//快速冪
return sum;
}int
main()
if(hashw==hasht)
ans++
;long
long cnt=
quickpow((
long
long
)base,lenw-1)
;//預處理b^n
for(
int i=lenw;i++i)
//t向後找子串去和w比較
printf
("%d\n"
,ans);}
return0;
}
/**
遞推預處理,未取模
跑了 235ms
*/#include
#include
#include
#include
#include
typedef
unsigned
long
long ull;
using
namespace std;
const
int maxn=
1e6+5;
const
int maxm=
1e4+5;
const
int base=29;
ull pow[maxm]
;char w[maxm]
,t[maxn]
;ull hasht[maxn]
;int n;
ull hashs;
int ans;
void
init()
intmain()
printf
("%d\n"
,ans);}
return0;
}
字串hash,在比賽中還是經常出現的,因為用map可能會tle或者mle。在一些題目裡,是作為某一關鍵的步驟。所以,多練多用,才能熟能生巧,在比賽中靈活運用。
字串Hash入門
字串hash可以通俗的理解為,把乙個字串轉換為乙個整數。但是根據以前學習的hash,會有衝突。但是當我們取p為131 或者13331 mod取2 64時 99.99 的情況是不會有衝突的 hash公式 unsigned long long hash n hash i hash i 1 p id s ...
Hash 字串 字串雜湊
luo gu luogu luogup 3370 p3370 p337 0如題,給定n個字串 第i個字串長度為mi,字串內包含數字 大小寫字母 請求出n個字串中共有多少個不同的字串。第一行包含乙個整數n,為字串的個數。接下來n行每行包含乙個字串,為所提供的字串。輸出包含一行,包含乙個整數,為不同的字...
白兔的字串 字串hash
原題 一道典型的字串hash,至於hash,這裡講的非常好。一開始用map函式一直超時,後來改用unordered map就過了,至於這2個map的區別,這裡講的挺清楚的。之後去查了一下其它方法,發現還有一種方法是手寫map函式 強 指明 大佬 unordered map是跑了600ms,重寫跑了1...