算法总结篇---哈希和哈希表


目录
  • 哈希和哈希表
    • 单哈希
    • 多哈希
    • 哈希表
    • 多哈希哈希表
    • 关于哈希函数
    • 字符串哈希
    • 例题
      • Oulipo
      • 图书管理
      • Power Strings
      • [BalticOI 2014 Day1] Three Friends
      • P3538 [POI2012]OKR-A Horrible Poem

哈希和哈希表

什么是哈希啊?

哈希是一种用来统计复杂数据的不完美算法,或者说思想,构造一个哈希函数将原始数据映射成便于统计的信息上,在映射过程中会损失部分信息。类似于离散化,仅保留大小关系。

举个栗子:

维护一个数据结构,支持插入一个数,查询一个的数在这个数据结构中的个数,数的大小 \(\le 2^{63} - 1\)

单哈希

把插入的一个数对一个不是很大的数取模,令新数代替原数。
如果两个数的余数相等,则认为它们两个相等
开一个 \(cnt\) 数组统计个数

多哈希

同时对多个模数取模,判重时判取模后的所有数是否全部相等,
实现时可以定义一个结构体,用 \(set/map\) 维护,或写哈希表。

正确性大幅增加,但仍不是完全正确。
一般写双哈希就够了,卡不掉。

哈希表

考虑单哈希。
不把余数相等的一些数直接看做相等,开个链表把它们链起来。
判重时找到查询的数的余数对应的链表,遍历所有元素判重。
可以用邻接表或 vector 实现。

随机数据下链表最大长度(每次判重的复杂度)期望 O(\frac{n}{mod})。
牺牲了时间,保证了正确性

多哈希哈希表

可以用多哈希使数的分布更加均匀。
一般做法是对哈希得到的多个余数再进行哈希。

关于哈希函数

对应的哈希函数相等是两元素相等的必要条件

可以随便构造

构造哈希函数的方式多种多样,模一个数,乘一个数,加一个数,甚至更复杂的关系

只要正确性高就行,或者函数符合您的品味

字符串哈希

用于判重字符串
将一串字符映射成一个整数再进行判断

由于字符串是具有前后关系的,一般按下述方法构造:
选取两个合适的互质常数 \(b\)\(h (b < h)\), 假设有一个字符串 \(C = c_1c_2···c_m\),那么我们定义哈希函数:

\[H(C) = (c_1b^{m - 1} + c_2b^{m - 2} + ···+c_mb^{0}) \mod h \]

考虑递推实现,设 \(H(C, k)\) 为前 \(k\) 个字符构成的字符串的哈希值,则:

\[H(C, k + 1) = H(C, k) \times b + c_{k + 1} \]

通常,题目要求的是判断主串的一段字符与另一个匹配串是否匹配,即判断字符串 \(C = c_1c_2···c_m\) 从位置 \(k + 1\) 开始的长度为 \(n\) 的子串 \(C^{'} = c_{k + 1}c_{k + 2}···c_{k + n}\) 的哈希值与另一匹配串 \(S = s_1s_2···s_n\) 的哈希值是否相等,则:

\[H(C_{'}) = H(C, k + n) - H(C, k) \times b^{n} \]

只要预求得 \(b^{n}\) ,就能 \(O(1)\) 判断了

总时间复杂度 \(O(n + m)\)

例题

Oulipo

板子题

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include
#include
#include
#include
#define LL long long
#define orz cout<<"lkp AK IOI!"<>= 1;
	}
	return res;
}

#undef int 

int main()
{
#define int long long
//	init();
	cin>>A + 1;
	cin>>B + 1;
	int lenA = strlen(A + 1), lenB = strlen(B + 1);
	pow = quickpow(b, lenB, mod);
	for(int i = 1; i <= lenA; ++i){ H[i] = (H[i - 1] * b % mod + A[i]) % mod; }
	for(int i = 1; i <= lenB; ++i){ sum = (sum * b % mod + B[i]) % mod; }
	cnt = 0;
	for(int i = 0; i + lenB <= lenA; ++i){
//		cout<<(H[i + lenB] - H[i] * pow % mod + mod) % mod<

图书管理

发现图书只有加入没有拿出,用字符串哈希转换后和上面的例题类似

bool 数组来表示其是否加入,\(O(1)\) 查询

考虑用双哈希优化,会使重复的可能性降到很低

我这里只用了单哈希,开到一亿多才卡过去
成为所有提交记录中用时最长空间最长的一份代码

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include
#include
#include
#include
#define LL long long
#define orz cout<<"lkp AK IOI!"<>opt;
		gets(nam + 1);
		len = strlen(nam + 1);
		for(int j = 1; j <= len; ++j){ sum = (sum * b % mod + nam[j]) % mod; }
		if(opt[0] == 'a') vis[sum] = 1;
		if(opt[0] == 'f') 
			if(vis[sum]) printf("yes\n");
			else printf("no\n");
	}
	return 0;
}

Power Strings

比较好出思路,枚举重复的字符串的长度,因为长度必须是总长度的因子,所以 \(O(len)\) 枚举即可,然后扫一遍看看是否符合条件,从小到大最先遇到的一定是答案

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include
#include
#include
#include
#define LL long long
#define orz cout<<"lkp AK IOI!"<>(s + 1)) && s[1] != '.'){
		len = strlen(s + 1);
		for(int i = 1; i <= len; ++i){
			H[i] = (H[i - 1] * b % mod + s[i]) % mod;
		}
		for(int i = 1; i <= len; ++i){
			if(len % i) continue;
			if(check(i)) {
				printf("%d\n", len / i);
				break;
			}
		}
	}
	return 0;
}

[BalticOI 2014 Day1] Three Friends

相关 \(Solution\) 请跳转我的这篇

P3538 [POI2012]OKR-A Horrible Poem

来自两篇题解的思路,可以结合着理解一下,另外loj上这题卡我模数和进制数

1、循环节一定是长度的约数
2、如果n是一个循环节,那么k*n也必定是一个循环节(关键所在)
3、n是[l,r]这一段的循环节  的充要条件是  [l,r-n]和[l+n,r]相同(利用这个性质我们在判断是否为循环节是可以做到O(1))  
所以我们可以在求出这个区间的长度之后,判断它的每个约数是否是循环节(应用性质3),并且因为性质1,它的约数是循环节,原串一定也是。
循环节的长度的循环次数都一定是总长的约数
我的做法是把总长除掉循环次数
先把len分解质因数
(线性筛质数,并记录下每个数的最小质因子加速分解,这已经是常规操作了)
因为最小循环节的倍数也是循环节
所以从len开始试除每个质因子并判断(你可以理解为len的因子分为循环节的因子和循环次数的因子,要把循环次数的因子除掉)
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include
#include
#include
#include
#define LL long long
#define LLL unsigned long long
#define orz cout<<"lkp AK IOI!"<> (s + 1);
	Pow[0] = 1;
	for(LL i = 1; i <= n; ++i){ Pow[i] = Pow[i - 1] * b, H[i] = H[i - 1] * b + s[i]; }
	m = read();
	for(LL i = 1, l, r, len, ans; i <= m; ++i){
		l = read(), r = read();
		ans = len = r - l + 1;
		while(len > 1){
			LL k = ans / prim[len];
			len /= prim[len];
			if(H[r - k] - H[l - 1] * Pow[r - k - l + 1] == H[r] - H[l - 1 + k] * Pow[r - k - l + 1]){
				ans = k;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}