字符串Hash

【原理】

字符串Hash函數把一個任意長度的字符串映射成一個非負整數,並且其衝突概率幾乎爲 00

  1. 取一固定值 PP,把字符串看作 PP 進制數,並分配一個大於 00 的數值,代表每種字符。一般來說,我們分配的數值都遠小於P。例如,對於小寫字母構成的字符串,可以令 a=1,b=2,...,z=26a=1,b=2,...,z=26
  2. 取一固定值 MM,求出該 PP 進制數對 MM 的餘數,作爲該字符串的Hash值。

一般來說, 我們取 P=131P=131P=13331P= 13331,此時Hash值產生衝突的概率極低,只要Hash值相同,我們就可以認爲原字符串是相等的。

通常我們取 M=264M = 2^{64},即直接使用 unsigned long long 類型存儲這個Hash值,在計算時不處理算術溢出問題,產生溢出時相當於自動對 2642^{64} 取模,這樣可以避免低效的取模運算。

int 溢出相當於 mod 2312^{31}
unsigned int 溢出相當於 mod 2322^{32}
unsigned long long 溢出相當於 mod 2642^{64}

除了在極特殊構造的數據上,上述Hash算法很難產生衝突,一般情況下上述Hash算法完全可以出現在題目的標準解答中。我們還可以多取一些恰當的 PPMM 的值(例如大質數),多進行幾組Hash運算,當結果都相同時才認爲原字符串相等,就更加難以構造出使這個Hash產生錯誤的數據。

【應用】

對字符串的各種操作,都可以直接對 PP 進制數進行算術運算反映到Hash值上。

如果我們已知字符串 SS 的Hash值爲 H(S)H(S),那麼在 SS 後添加一個字符 cc 構成的新字符串 S+cS+c 的Hash值就是 H(S+c)=(H(S)P+value[c]) mod MH(S + c) = (H(S)*P+value[c])\ mod\ M。其中乘 PP 就相當於 PP 進制下的左移運算,value[c]value[c]是我們爲 cc 選定的代表數值。

如果我們已知字符串 SS 的Hash值爲 H(S)H(S),字符串 S+TS+T 的Hash值爲 H(S+T)H(S +T),那麼字符串 TT 的Hash值 H(T)=(H(S+T)H(S)Plength(T)) mod MH(T) = (H(S+T) - H(S)* P^{length(T)}) \ mod\ M。這就相當於通過 PP 進制下在 SS 後邊補 00 的方式把 SS 左移到與 S+TS+T 的左端對齊,然後二者相減就得到了 H(T)H(T)

例如,S="abc", c="d", T="xyz"S = "abc",\ c= "d",\ T = "xyz"

則:SS 表示爲 PP 進制數:1 2 31\ 2\ 3

H(S)=1P2+2P+3H(S)=1*P^2+2*P +3

H(S+c)=1P3+2P2+3P+4=H(S)P+4H(S+c)=1*P^3 +2*P^2+3*P+4=H(S)*P+4

S+TS+T 表示爲 PP 進制數:1 2 3 24 25 261\ 2\ 3\ 24\ 25\ 26

H(S+T)=1P5+2P4+3p3+24p2+25P+26H(S+T)= 1*P^5+2*P^4+3*p^3+ 24*p^2+ 25*P+ 26

SSPP 進制下左移 length(T)length(T) 位:1 2 3 0 0 01\ 2\ 3\ 0\ 0\ 0

二者相減就是 TT 表示爲 PP 進制數:24 25 2624\ 25\ 26

H(T)=H(S+T)(1P2+2P+3)P3=24P2+25P+26H(T)= H(S+T)-(1*P^2+2*P+3)*P^3= 24*P^2+ 25*P+ 26

根據上面兩種操作,我們可以通過 O(N)O(N) 的時間預處理字符串所有前綴Hash值,並在 O(1)O(1) 的時間內查詢它的任意子串的Hash值

【模板】

typedef unsigned long long ull;
const int maxn = 1000010, base = 131;

char str[maxn];
ull h[maxn], p[maxn];

//O(1)查詢[L,R]子串的哈希值 
ull get(int L, int R)
{
	return h[R] - h[L-1] * p[R-L+1];
}

int main()
{
	scanf("%s", str + 1);
	int n = strlen(str + 1);
	
	p[0] = 1;
	for(int i = 1; i <= n; ++i)
	{
		h[i] = h[i-1] * base + str[i] - 'a' + 1;
		p[i] = p[i-1] * base;
	}
	
	int m;
	scanf("%d", &m);
	while(m--)			//m次查詢 
	{
		int L, R;
		scanf("%d%d", &L, &R);
		printf("%llu\n", get(L,R));
	}
	return 0;
}
發佈了734 篇原創文章 · 獲贊 112 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章