开放寻址法:
开放寻址法的数组长度最好是题目哈希的数据范围的2-3倍,其次模的那个数应该是符合前面这个条件的第一个质数且离2的幂次尽量得远(这样冲突会比较少)
找质数:
比如题目哈希的数据范围是1e5,那我们的N首先为2e5,然后再找大于2e5的第一个质数,N即为该数,下面为找质数的代码
for(int i = 2e5; ; i++)
{
bool flag = true;
for(int j = 2; j * j <= i; j++)
if(i % j == 0)
{
flag = false;
break;
}
if(flag)
{
cout << i << endl; //输出的i即为大于2e5的第一个质数
break;
}
}
开放寻址法的核心是find函数:如果已经存在x的话,就返回x的映射k的值,若不存在,就返回x应该放的值,也就是k;
int find(int x)
{
int k = (x % N + N) % N;
//如果x是负数的话,直接%N是一个负数,但放的地址都是正的,所以先%N再+N,最后再%N;
while(h[k] != null && h[k != x) //这个坑位上有人,并且不是自己,就要找下一个坑位;
{
k++;
if( k == N) k = 0;
//如果k已经找到最后一个位置了的话,就让k从第一个位置开始找,是一个循环的过程;
}
return k;
//如果存在x的话,就返回x的映射k的值,若不存在,就返回x应该放的值,也就是k;
}
.......................................................................................................
字符串哈希(前缀和字符串):
作用:
一般是判断两个区间的子字符串是否相等
求字符串哈希的时候首先预处理出来所有前缀的哈希:
比如:h[1]表示前1个字符的哈希值,h[2]表示前2个字符的哈希值,以此类推,h[0] 特殊处理=0
那么如何来定义某个前缀的哈希值:
把字符串看作是一个P进制的数,每一位上的字母表示P进制上的每一位数字
把字符串的哈希值算出来后(因为该值很大,所以还要模上一个Q)
注意:
1.一般情况下不要把某个字母映射成0
2.哈希数字的时候是有冲突的且要解决冲突,即上面的开放寻址法;但哈希字符串的时候是假设人品足够好,不会出现冲突
3.P取131或 13331,Q取2^64,在这么取的情况下,基本可以假定不会出现冲突
4.因为unsigned long long 是2^64,所以用ULL来存h[],这样就不需要取模了(溢出相当于取模)
我们可以利用前面求出来的前缀哈希,可以求出任一字串的哈希:
比如想求出[L,R]的哈希值,因为预处理,已知[1,R]和[1,L-1]的哈希值,即h[R]和h[L-1]
[1,R]: 最高位是P^(R-1) (1对应的位置),最低位是P^0 (R对应的位置)
[1,L-1]:最高位是P^(L-2) (1对应的位置),最低位是P^0 (L-1对应的位置)
1.所以要先把[1,L-1]这段往左移,(为什么要向左移,因为h[R]和h[L-1]的前L-1个字母是相同的,但因为位数不同,乘的权值也不同即乘P的次方不一样,所以要使前L-1个字母往左移,这样他们两个前L-1个字母都对应的同样的权值,最后再相减)使最高位对齐,即h[L-1] * (R-L+1)
2.[L,R]的哈希值即为 h[R]-h[L-1] * (R-L+1)
3.后面乘的这个(R-L+1)用一个p[]预处理出来
预处理代码:
ULL h[N], p[N];
p[0]=1;
for(int i=1;i<=n;i++)
{
p[i]=p[i-1]*P; //p[i]=P的i次方;
h[i]=h[i-1]*P+str[i]; //这里的str[i] 是对应的ASCII码值;
}
判断代码:
ULL get(int l, int r)
{
return h[r] - h[l-1] * p[r-l+1]; //这里就是前面预处理p[]的作用,p[i]表示P^i
}
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");
}
...........................................................................................................................................................
另外还要说一种字符串哈希(区别于上面的前缀和哈希):
作用:
这种字符串哈希作用一般是预处理打表,差不多就是不需要预处理前缀和,而是直接求得每个字符串的哈希值,然后打表
下面给个代码:
ULL Hash(char *str)
{
ULL res = 0;
while(*str) res = res*P+*str, ++str; //这步
return res;
}
int find(char *str)
{
int p = Hash(str) % MOD;
//这里就和开放寻址法类似:ht[p][0]不为0且p位置存的不是他本身(用的是strcmp函数)p就++,直到
找到位置
while(ht[p][0] && strcmp(str, ht[p])) p++;
return p;
}