Hash表又称散列表,一般由Hash函数(散列函数)与链表结构共同实现。与离散化思想类似。
解决冲突:有一种称为“开散列”的解决方案是,建立一个邻接表结构,以Hash函数的值域作为表头数组head,映射后的值相同的原始信息被分为同一类,构成一个链表接在对应的表头之后,链表的节点上可以保存原始信息和一些统计数据。
设计Hash函数为 H ( x ) = ( x m o d P ) + 1 H(x)=(x\mod P)+1 H(x)=(xmodP)+1,其中P是一个比较大的质数,但不超过N。显然,这个Hash函数把数列A分成P类,我们可以依次考虑数列中的每个数A[i],定位到head[A[i]]这个表头所指向的链表。如果该链表中不包含A[i],我们就在表头后插入一个新节点A[i],并在该节点上记录A[i]出现了1次,否则我们就直接找到已经存在的A[i]节点将其出现次数加1。因为整数序列A是随机的,所以最终所有A[i]会比较均匀地分散在各个表头之后。
对于非随机的数列,我们可以设计更好的Hash函数来保证其时间复杂度。
同样地,如果我们需要维护的是比大整数复杂得多的信息的某些性质(如是否存在、出现次数等),也可以用Hash表来解决。字符串就是一种比较一般化的信息。
题意 :
思路 :
sizeof a,因为,我们定义的a数组有10个长度#include
#include
using namespace std;
const int N = 1e5 + 10, P = 99991;
int n;
int head[N], ne[N], tot, snow[N][6];
int H(int *a) {
int sum = 0, mul = 1;
for (int i = 0; i < 6; ++ i) {
sum = (sum + a[i]) % P;
mul = (long long)mul * a[i] % P;
}
return (sum + mul) % P;
}
bool equal(int *a, int *b) {
for (int i = 0; i < 6; ++ i) {
for (int j = 0; j < 6; ++ j) {
bool eq = 1;
for (int k = 0; k < 6; ++ k) {
if (a[(i + k) % 6] != b[(j + k) % 6]) eq = 0;
}
if (eq) return 1;
eq = 1;
for (int k = 0; k < 6; ++ k) {
if (a[(i + k) % 6] != b[(j - k + 6) % 6]) eq = 0;
}
if (eq) return 1;
}
}
return 0;
}
bool insert(int *a) {
int val = H(a);
for (int i = head[val]; i; i = ne[i]) {
if (equal(snow[i], a)) return 1;
}
++ tot;
memcpy(snow[tot], a, 6 * sizeof(int));
ne[tot] = head[val];
head[val] = tot;
return 0;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++ i) {
int a[10];
for (int j = 0; j < 6; ++ j) scanf("%d", &a[j]);
if (insert(a)) {
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
}
下面介绍的字符串Hash函数把一个任意长度的字符串映射成一个非负整数,并且其冲突概率几乎为零。
取一固定值P,把字符串看作P进制数,并分配一个大于0的数值,代表每种字符。一般来说,我们分配的数值都远小于P。例如,对于小写字母构成的字符串,可以令a = 1,b = 2,…,z = 26。取一固定值M,求出该P进制数对M的余数,作为该字符串的Hash值(->(P进制数->取模))。
一般来说,我们取 P = 131 或 P = 13331,此时Hash值产生冲突的概率极低,只要Hash值相同,我们就可以认为原字符串是相等的。通常我们取M = 2^{64},即直接使用unsigned long long类型存储这个Hash值,在计算时不处理算术溢出问题,产生溢出时相当于自动对
2
64
2^{64}
264取模,这样可以避免低效的取模(mod)运算。
除了在极特殊构造的数据上,上述Hash算法很难产生冲突,一般情况下上述Hash算法完全可以出现在题目的标准解答中。我们还可以多取一些恰当的P和M的值(例如大质数),多进行几组Hash运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个Hash产生错误的数据。
对字符串的各种操作,都可以直接对P进制数进行算术运算反映到Hash值上。
如果我们已知字符串S的Hash值为H(S),那么在S后添加一个字符串构成的新字符串S+c的Hash值就是H(S+c)=(H(s) * P + value[c]) mod M。其中,乘P就相当于P进制下的左移运算,value[c]是我们为c选定的代表数值
如果我们已知字符串S的Hash值为H(S),字符串S+T的Hash值为H(S+T),那么字符串T的Hash值H(T) = (H(S+T) - H(S) * P l e n g t h ( T ) P^{length(T)} Plength(T)) mod M。
通过上面两种操作,我们可以通过O(N)的时间预处理字符串所有前缀Hash值,并在O(1)的时间内查询它的任意子串的Hash值。
题意 :
#include
#include // strlen的头文件
using namespace std;
typedef unsigned long long ULL; // ULL
const int N = 1e6 + 10, P = 131; // P可以是int
char str[N]; // char数组的方式
ULL h[N], power[N]; // h和power都是ULL
ULL get(int l, int r) {
return h[r] - h[l - 1] * power[r - l + 1]; // 公式
}
int main() {
scanf("%s", str + 1); // 输入char数组,注意下标从1开始
int n = strlen(str + 1); // 得到char数组长度
power[0] = 1; // 注意乘法数组初始化!
for (int i = 1; i <= n; ++ i) {
h[i] = h[i - 1] * P + str[i] - 'a' + 1; // 最后+1,因为否则a就是0了
power[i] = power[i - 1] * P;
}
int m;
scanf("%d", &m);
while (m -- ) {
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if (get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
}
题意 :
思路 :
O(N)预处理前缀Hash值后,可以O(1)计算任意字串的Hash值;类似地,我们可以倒着做一遍预处理,就可以O(1)计算任意字串倒着读的Hash值#include
#include
using namespace std;
typedef unsigned long long ull;
const int N = 2e6 + 10, P = 131;
char s[N];
ull h1[N], h2[N], p[N];
ull get(ull h[], int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
int main() {
int cnt = 0;
while (scanf("%s", s + 1) && strcmp(s + 1, "END")) {
int n = strlen(s + 1) * 2;
for (int i = n; i; i -= 2) {
s[i] = s[i / 2];
s[i - 1] = 'z' + 1;
}
p[0] = 1ull;
for (int i = 1, j = n; i <= n; ++ i, -- j) {
h1[i] = h1[i - 1] * P + s[i] - 'a' + 1;
h2[i] = h2[i - 1] * P + s[j] - 'a' + 1;
p[i] = p[i - 1] * P;
}
int ans = 0;
for (int i = 1; i <= n; ++ i) {
int l = 0, r = min(i - 1, n - i);
while (l < r) {
int mid = (l + r + 1) >> 1;
if (get(h1, i - mid, i - 1) == get(h2, n + 1 - (i + mid), n + 1 - (i + 1))) {
l = mid;
} else {
r = mid - 1;
}
}
if (s[i - l] <= 'z') {
ans = max(ans, l + 1);
} else {
ans = max(ans, l);
}
}
printf("Case %d: %d\n", ++ cnt, ans);
}
}