zoukankan      html  css  js  c++  java
  • 算法总结篇---哈希和哈希表

    哈希和哈希表

    什么是哈希啊?

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

    举个栗子:

    维护一个数据结构,支持插入一个数,查询一个的数在这个数据结构中的个数,数的大小 (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) imes 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) imes b^{n} ]

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

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

    例题

    Oulipo

    板子题

    /*
    Work by: Suzt_ilymics
    Knowledge: ??
    Time: O(??)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    #define int long long
    
    using namespace std;
    const int MAXN = 1e6+10;
    const int INF = 1;
    const int mod = 1e9+9;
    const int b = 1e9+7;
    
    char A[MAXN], B[MAXN];
    int H[MAXN], h[MAXN], pow, cnt = 0, sum = 0;
    
    int read(){
    	int s = 0, f = 0;
    	char ch = getchar();
    	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    	return f ? -s : s;
    }
    
    int quickpow(int x, int p, int mod){
    	int res = 1;
    	while(p){
    		if(p & 1) res = res * x % mod;
    		x = x * x % mod;
    		p >>= 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<<endl;
    		if((H[i + lenB] - H[i] * pow % mod + mod) % mod == sum){
    //			orz;
    			cnt++;
    		}
    	}
    	printf("%d", cnt);
    	return 0;
    }
    

    图书管理

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

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

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

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

    /*
    Work by: Suzt_ilymics
    Knowledge: ??
    Time: O(??)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 3e4+4;
    const int INF = 1;
    const int mod = 101451419;
    const int b = 1e9 + 7;
    
    LL n, stc[MAXN], sc = 0;
    bool vis[101452419];
    char nam[210], opt[10];
    
    int read(){
    	int s = 0, f = 0;
    	char ch = getchar();
    	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    	return f ? -s : s;
    }
    
    int main()
    {
    	n = read();
    	for(int i = 1, len; i <= n; ++i){
    		LL sum = 0;
    		cin>>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
    ");
    			else printf("no
    ");
    	}
    	return 0;
    }
    
    

    Power Strings

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

    /*
    Work by: Suzt_ilymics
    Knowledge: ??
    Time: O(??)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e6+6;
    const int INF = 1;
    const int mod = 1e9+9;
    const int b = 1e7+7;
    
    char s[MAXN];
    LL pow[MAXN], H[MAXN], sum;
    int len;
    
    int read(){
    	int s = 0, f = 0;
    	char ch = getchar();
    	while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    	return f ? -s : s;
    }
    
    void init(){
    	pow[0] = 1;
    	for(int i = 1; i <= 1e6; ++i){ pow[i] = pow[i - 1] * b % mod; }
    }
    
    bool check(int mid){
    	sum = H[mid];
    	for(int i = 0; i <= len - 1; i += mid){
    		if((H[i + mid] - H[i] * pow[mid] % mod + mod) % mod != sum) return 0;
    	}
    	return 1;
    }
    
    int main()
    {
    	init();
    	while((cin>>(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
    ", 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<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define LLL unsigned long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 5e5+10;
    const int INF = 1;
    const int mod = 1e9+7;
    const int b = 7;
    
    LL n, m;
    char s[MAXN];
    LLL Pow[MAXN], H[MAXN], sum;
    LL prim[MAXN], sc = 0;
    bool vis[MAXN], flag;
    
    LL read(){
    	LL s = 0, f = 0;
    	char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) s = (s << 3) + (s << 1) + ch - '0', ch = getchar();
    	return f ? -s : s;
    }
    
    void init(){
    	for(LL i = 2; i <= n; ++i){
    		if(vis[i]) continue;
    		for(LL j = 1; i * j <= n; ++j){
    			LL t = i * j;
    			if(vis[t]) continue;
    			vis[t] = true;
    			prim[t] = i;
    		}
    	}
    }
    
    int main()
    {
    	n = read();
    	init();
    	cin >> (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
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    xxx.app已损坏,打不开.你应该将它移到废纸篓-已解决
    idea 配置maven一直停留在loading archetype list
    pom.xml 识别xml文件
    idea .defaultMessage
    处理GitHub上的不允许100MB大文件上传
    Makefile 简易教程
    Android:用Intent传送图片
    Android: ListView的使用(列表控件)
    Android: SharedPreferences的简单使用(数据可持久化)
    Andriod:一个Activity向另一个Activity传递数据
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14223275.html
Copyright © 2011-2022 走看看