1 哈希
哈希(Hash)算法是一种能够将任意长度的输入转换成固定长度数据(哈希值)的算法。
它的主要特点如下:
- 输入的长度任意,但哈希值长度一定。
- 相同的数据经过哈希后哈希值相同,反之则不同。
- 哈希值的计算是单向的,也就是说,不能通过哈希值来得到原始数据。因此,哈希也被用于密码学,它可以增加密码的安全性和验证密码是否被篡改。
哈希的简单过程如下:
原始数据:12 8 9 14 3
以 7 7 7 为固定长度(通常是一个较大的质数),把每个数字填到对 7 7 7 取余后所对应的位置上:
0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
14 | 8 | 9 | 3 | 12 |
哈希冲突: 两个数字需要填在相同的位置上,产生冲突。
例如,原始数据中再添加一个数字 19
,此时 19
与 12
冲突。
方法1 开放地址法: 在冲突位置开放溢出地址空间。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
14 | 8 | 9 | 3 | 12(19) |
方法2 开放寻址法: 从冲突位置向后找空余的地址,如果到尾部则从头开始,直到找到空间存放。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
14 | 8 | 9 | 3 | 12 | 19 |
在哈希中,存值的复杂度为 O ( 1 ) O(1) O(1),而查找数据的过程,也可以看做近似于 O ( 1 ) O(1) O(1)。
2 字符串哈希
字符串哈希: 将字符串每个字符转换为 ASCII \texttt{ASCII} ASCII 码,得到一个 128 128 128 进制数,将其转为 10 10 10 进制数,即为字符串哈希值。
例:hello!
⇒
\Rightarrow
⇒
(
104
101
108
108
111
)
128
(104\ 101\ 108\ 108\ 111)_{128}
(104 101 108 108 111)128
=
=
=
(
85666186
)
10
(85666186)_{10}
(85666186)10
代码如下:
long long Hash(string s) {
has[0] = s[0] % mod;
// pw[0] = 1;
for (int i = 1; i < s.size(); i++)
has[i] = (has[i - 1] * 128 + s[i]) % mod,
// pw[i] = pw[i - 1] * 128 % mod;
return has[s.size() - 1];
}
另外,易得求子串哈希值的方法:
ll get(int l, int r) {
return (has[r] - has[l] * pw[r - l + 1] % mod + mod) % mod;
}
其中, p w i = 12 8 i pw_i=128^{i} pwi=128i。
通过字符串哈希,我们可以比较两个字符串是否完全相同。
3 例题1:查词典
题目描述
输入 n n n 行,每行一个英语单词和一个外文单词。
查询 m m m 次,查找外文单词的英文翻译(没有输出
eh
)。
题解
求出每个外文单词的哈希值,查询时比较哈希值,相同时输出英文。
部分代码如下:
int main() {
CLOSE;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i] >> t;
H[i] = Hash(t);
}
cin >> m;
while (m--) {
cin >> t;
ll k = Hash(t), flag = 0;
for (int i = 1; i <= n; i++) {
if (H[i] == k) {
cout << s[i] << endl;
flag = 1;
break;
}
}
if (!flag) cout << "eh" << endl;
}
return 0;
}
4 例题2:消失的密文
题目描述
给定密码表 S S S ( ∣ S ∣ = 26 ) (\left|S\right|=26) (∣S∣=26), 先给出一条残缺后仅剩 n n n 个字母的密文,前一半为密文,后一半为前者的明文,但后面部分内容缺失,输出尽可能短的未残缺密文。
题解
显然,要找到密文的一个最短的前缀,使得前缀等于后缀的密文(前缀后缀等长)。
abcdefghijklmnopqrstuvwxyz
abcdab
qwertyuiopasdfghjklzxcvbnm
qwertabcde
样例中,第一组前缀 ab
是后缀 ab
的密文,则 abcd
为密文,还原后的完整密文是 abcdabcd
;
第二种前缀 qwert
是后缀 abcde
的密文,则 qwert
为密文,还原后的完整密文是 qwertabcde
。
代码如下:
#include <bits/stdc++.h>
#define endl '\n'
#define file(FILENAME) \
freopen(FILENAME ".in", "r", stdin), freopen(FILENAME ".out", "w", stdout)
#define CLOSE \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0)
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int T, n;
string s, t;
int main() {
CLOSE;
cin >> T;
while (T--) {
map<char, char> mp;
cin >> s >> t;
for (int i = 0; i < 26; ++i)
mp[s[i]] = 'a' + i; //明文对照表
n = t.size();
ll ans1 = 0, jz = 1, ans2 = 0, end = n;
for (int i = 0; i < n / 2; ++i) {
ans1 = (ans1 + t[i] * jz) % mod; //计算前缀的哈希值
jz = jz * 128 % mod;
ans2 = (ans2 * 128 + s[t[n - i - 1] - 'a']) % mod; //计算后缀的哈希值
if (ans1 == ans2)
end = n - i - 1;
}
for (int i = 0; i < end; ++i)
cout << t[i];
for (int i = 0; i < end; ++i)
cout << mp[t[i]]; //补齐明文
cout << endl;
mp.clear();
}
return 0;
}