哈希(HASH)哈希(HASH)本质上是一种映射。
引入1给定 n n n 个正整数,这些正整数的值域均为 [ 1 , 1 0 6 ) [1,10^6) [ 1 , 1 0 6 ) ,让你把这些数去重后按从小到大排序后输出。
方法用一个桶来统计每一个数的次数,最后循环值域,如果次数不为 0 0 0 ,就输出即可。
时间复杂度:O ( n ) O(n) O ( n ) ,空间复杂度:O ( 1 0 6 ) O(10^6) O ( 1 0 6 ) 。
引入2给定 n n n 个正整数,这些正整数的值域均为 [ 1 , 1 0 9 ) [1,10^9) [ 1 , 1 0 9 ) ,让你把这些数去重后按从小到大排序后输出。
这题发现值域到了 1 0 9 10^9 1 0 9 ,很明显无法用桶排来做。
方法1用STL-unique来写,时间复杂度大概为 O ( n ) O(n) O ( n ) ,可以过。
不过大部分题目都无法用unique,就有了新的算法-哈希。
方法2-哈希算法我们用一个函数 H H H ,将一个很大的数 x x x 变为一个可以用数组存下的数。
一般都将哈希函数 H ( x ) H(x) H ( x ) 定义为 x m o d P x\bmod P x m o d P (P P P 为一个质数)。
最后将哈希之后的数做桶排即可。
哈希冲突我们发现如果两个数 x , y x,y x , y 使 H ( x ) = H ( y ) H(x)=H(y) H ( x ) = H ( y ) ,此时发现哈希之后冲突了,答案就错误。此时就叫哈希冲突 、
解决哈希冲突我们用一个链表处理 0 ∼ P − 1 0\sim P-1 0 ∼ P − 1 的模数情况。即,将 x x x 插入到 H ( x ) H(x) H ( x ) 的链表下。
每次插入时遍历 H ( x ) H(x) H ( x ) 的链表,如果没有重复就插入,否则就不插入链表。
时间复杂度O ( n ⋅ l e n ) ≈ O ( n ⋅ n P ) O(n\cdot len) \approx O(n\cdot \dfrac{n}{P}) O ( n ⋅ l e n ) ≈ O ( n ⋅ P n )
当 P ≈ n 时, O ( n ⋅ n P ) ≈ O ( n ) \text{当}P\approx n\text{时,}O(n\cdot \dfrac{n}{P})\approx O(n) 当 P ≈ n 时, O ( n ⋅ P n ) ≈ O ( n ) 。
此时的时间复杂度就十分优秀了。
模板代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std;const int MOD = 999983 ;int n, x, ans; vector <int > G[MOD + 5 ];int Hash (int x) { return x % MOD; }int insert (int x) { int val = Hash (x); for (int i = 0 ; i < G[val].size (); ++i) { if (G[val][i] == x) return 0 ; } G[val].push_back (x); return 1 ; }int main () { cin >> n; for (int i = 1 ; i <= n; ++i) { cin >> x; ans += insert (x); } cout << ans << endl; return 0 ; }
例题这题定义哈希函数 H ( x ) = x m o d P H(x)=x\bmod P H ( x ) = x m o d P ,如果过不了,别忘了考虑负数的情况。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <bits/stdc++.h> using namespace std;const int N = 5e4 + 5 ;const int MOD = 999983 ;int T, n, ans;int a[N]; vector <int > G[MOD + 5 ];int Hash (int x) { return (x % MOD + MOD) % MOD; }int insert (int x) { int val = Hash (x); for (int i = 0 ; i < G[val].size (); ++i) { if (G[val][i] == x) return 0 ; } G[val].push_back (x); return 1 ; }int main () { cin >> T; while (T--) { cin >> n; ans = 0 ; for (int i = 1 ; i <= n; ++i) { scanf ("%d" , &a[i]); } for (int i = 0 ; i < MOD; ++i) { G[i].clear (); } for (int i = 1 ; i <= n; ++i) { if (insert (a[i]) == 1 ) { cout << a[i] << " " ; } } cout << endl; } return 0 ; }
首先先看题目,看到有 x i = x j x_i=x_j x i = x j 时,就想到了用图论的并查集来做。即,先将所有 e = 1 e=1 e = 1 的情况中的 ( i , j ) (i,j) ( i , j ) 合并,最后合并完再枚举 e = 0 e=0 e = 0 的情况看是否在同一个连通块就行了。
可发现数据范围中的
1 ≤ i , j ≤ 1 0 9 1\leq i,j\leq 10^9 1 ≤ i , j ≤ 1 0 9
就知道用纯的并查集过不去。于是想到了用哈希将数的范围缩小,随后并查集维护即可。哈希函数一样是 H ( x ) = x m o d P H(x) = x\bmod P H ( x ) = x m o d P 。时间复杂度:O ( n ⋅ α ( n ) ) ≈ O ( n ) O(n\cdot\alpha(n))\approx O(n) O ( n ⋅ α ( n ) ) ≈ O ( n )
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <bits/stdc++.h> using namespace std;const int N = 1e5 + 5 ;const int MOD = 990887 ;int T, n;int x[N], y[N], opt[N];int fa[MOD + 5 ];int Hash (int x) { return x % MOD; }int find (int x) { if (x == fa[x]) return x; else return fa[x] = find (fa[x]); }void unionn (int x, int y) { int fx = find (x), fy = find (y); if (fx != fy) fa[fx] = fy; return ; }int main () { cin >> T; while (T--) { cin >> n; for (int i = 0 ; i < MOD; ++i) { fa[i] = i; } for (int i = 1 ; i <= n; ++i) { cin >> x[i] >> y[i] >> opt[i]; if (opt[i] == 1 ) unionn (Hash (x[i]), Hash (y[i])); } bool flag = false ; for (int i = 1 ; i <= n; ++i) { if (opt[i] == 0 ) { if (find (Hash (x[i])) == find (Hash (y[i]))){ puts ("NO" ); flag = true ; break ; } } } if (flag == false ) puts ("YES" ); } return 0 ; }
样例输入(POJ):
1 2 3 2 1 2 3 4 5 6 4 3 2 1 6 5
样例输出(POJ):
首先看到这题,发现每个雪花是一个含有六个数的序列,我们就定义这个序列的哈希值为这些数的和 m o d P \bmod P m o d P 的值,即:
H ( a ) = ∑ i = 1 6 a i H(a)=\sum_{i=1}^{6}{a_i} H ( a ) = i = 1 ∑ 6 a i
(当然也可以是别的,比如这个序列的和加这个序列的乘积 m o d P \bmod P m o d P 的值,只要与这个序列的顺序无关即可)
需要注意的是,判断两个雪花是否相同时,从某个节点,顺时针和逆时针都要比较才行。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <cstdio> #include <vector> #define int long long using namespace std;const int N = 1e5 + 5 ;const int MOD = 99991 ;int n;int a[N][20 ]; vector <int > G[MOD + 5 ];int Hash (int ind) { int sum = 0 ; for (int i = 1 ; i <= 6 ; ++i) { sum = (sum + a[ind][i]) % MOD; } return sum % MOD; }bool issame (int ind1, int ind2) { for (int i = 1 ; i <= 6 ; ++i) { for (int j = 1 ; j <= 6 ; ++j) { bool flag = true ; for (int k = 0 ; k < 6 ; ++k) { if (a[ind1][(i + k) % 6 + 1 ] != a[ind2][(j + k) % 6 + 1 ]) flag = false ; } if (flag == true ) return true ; flag = true ; for (int k = 0 ; k < 6 ; ++k) { if (a[ind1][(i + k) % 6 + 1 ] != a[ind2][(j - k + 6 ) % 6 + 1 ]) flag = false ; } if (flag == true ) return true ; } } return false ; }int insert (int ind) { int val = Hash (ind); for (int i = 0 ; i < G[val].size (); ++i) { if (issame (G[val][i], ind)) return 1 ; } G[val].push_back (ind); return 0 ; }int ans = 0 ;signed main () { cin >> n; for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= 6 ; ++j) { scanf ("%lld" , &a[i][j]); } if (insert (i) == 1 ) { puts ("Twin snowflakes found." ); return 0 ; } } puts ("No two snowflakes are alike." ); return 0 ; }
字符串哈希