专题·Hash(哈希)【including Hash表,字符串Hash,兔子与兔子

初见安~这里大概是本狸第一篇关于字符串的专题……QwQ

Hash表

首先Hash是个啥?【我也不认识】就是说定两个质数p和q,把整数或者是字符通过a *p%q之类的操作将其映射到另一个整数,用于查找。其映射值便是0~q - 1。 14的下标:head[0]

而Hash表就是一个类似于邻接表的结构,各个数据表头head就是该数据的映射值,即使是有映射值重复也没关系,像邻接表一样接在同一个表头后面即可。后期若要查找某个数是否存在,查询该数映射值的表头对应的一串数字,看里面有没有该数即可

可能这么说有点空,我们画个图吧——假设有hash(x) = x mod 7,有整数1,14,27,19898,232323,1982.则在hash表中就形如:

而在每一个的内部,则是形如:【邻接表式结构】

head数组就不画了……因为就相当于是邻接表的作用了。

这样的话我们要查询某个数是否存在之类的,就可以在其映射值将数据自动分类的情况下在近乎O(1)的时间复杂度内完成。当然,如果Hash函数设计得好,还可以让每个元素的hash值都不一样,效率可以更高。

放一个例题:给你一个数组,求去重过后的数组。有T组数据。

样例:

input:

2
11
1 2 18 3 3 19 2 3 6 5 4
6
1 2 3 4 5 6

output:

1 2 18 3 19 6 5 4
1 2 3 4 5 6

很明显,我们只需要一边读入,一边判断是否在hash表内存在,如果存在则continue;否则放入hash表中,输出这个数。

放一下hash表的存入和查询的模板代码:【hash有函数名重复,所以最好写Hash,避免不必要的CE】

const int p = 13331, q = 10101;//这里的hash函数为:(x + q) % p
 
struct node {int a, nxt;} Hash[maxn];//hash表的邻接表式结构,一个存值,一个存邻接表nxt
int head[p + 10], cnt = 0;
void insert(int x) {//hash的加入操作
    register int key = (x + q) % p;//key为hash函数值
    Hash[++cnt].a = x, Hash[cnt].nxt = head[key]; head[key] = cnt;//邻接表式存储
}
 
bool search(int x) {//hash表的查询操作
    register int key = (x + q) % p;//先得到函数值
    for(int i = head[key]; i; i = Hash[i].nxt) {//遍历这个函数后面对应的所有数
        if(Hash[i].a == x) return true;/如果找到了这个数
    }
    return false;
}

关于p和q,我说明一下:首先两个数一定是质数,其次,若hash(x) = (x + q) modp,则p越大越好,可以让hash值重复的概率尽量小,以提高查询效率。

Hash表的实现通常可以直接用STL中的map代劳,并且代码实现也要简单不少,但是如果真的遇到了刁钻的出题人卡map的常数【map常数不小】,那你也就只好手写Hash表了。

字符串Hash

就如我们文头介绍的——字符串Hash就是把字符串映射成一个整数,用于查询,比较之类的操作。

字符串的hash函数会比较特殊,因为重复的概率远大于普通整数,所以——我们会利用字符串长度划分梯度。

举个例子——有字符串s = aabcdij【均为小写字母】,设每个单独字符对应的hash值为s[i] - 'a' + 1。则有:【从1开始存的字符串】

\\p = 131,q = 998244353; \\Hash[1] = (Hash[0] * p + s[1] - 'a'+1) modq = 1 \\Hash[2] = (Hash[1]*p+s[2]-'a'+1)modq=132 \\Hash[3] = (Hash[2]*p+s[3]-'a'+1)modq=17294 \\Hash[4] = (Hash[3]*p+s[4]-'a'+1)modq =2265517 \\Hash[5] = (Hash[4]*p+s[5]-'a'+1)modq=296782731 \\......

也就是说,对于一个字符串中的每一个字符,若是第i个【从1开始数】,则对于整个字符串的Hash值的贡献为:(s[i] - 'a'+1) * p^{i - 1} modq

所以我们可以在O(strlen(n))的复杂度内求出整个字符串的Hash值。

 

那么怎么求任意子串的Hash值呢?

因为我们的Hash值很明显是一个前缀和的形式,所以很容易想到用Hash[r] - Hash[l - 1]。但是因为有梯度差,所以我们真正的值是:Hash[r] - Hash[l - 1] * p^{r - l +1}

如果不太好理解,我们可以换一种写法:

假设还是那个字符串:s = aabcdij,我们取l = 3, r = 5,也就是字符串bcd。

把上文的递推式展开的话就是这个样子的:

\\Hash[2]=1*p^1+1*p^0 \\Hash[3] = 1 * p^{2} + 1 * p^{1} + 2 * p^{0} \\Hash[4] = 1*p^3+1*p^2+2*p^1+3*p^0 \\Hash[5]=1*p^4+1*p^3+2*p^2+3*p^1+4*p^0

易得:字符串bcd的Hash值为2*p^2+3*p^1+4*p^0,也就是Hash[5] - Hash[2]*p^3

就这样。

最后良心告诫:strlen()这个函数,要调用的话记得存入整数里调用!!!每次求len的复杂度怎么都有On的,很容易就TLE了!!!牢记牢记。

我们来看个例题吧——


兔子与兔子

Description

很久很久以前,森林里住着一群兔子。
有一天,兔子们想要研究自己的DNA序列。
我们首先选取一个好长好长的DNA序列(小兔子是外星生物,DNA序列可能包含26个小写英文字母)。
然后我们每次选择两个区间,询问如果用两个区间里的DNA序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的DNA序列一模一样。

Input

第一行输入一个DNA字符串S。
第二行一个数字m,表示m次询问。
接下来m行,每行四个数字l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。
数据范围
1≤length(S),m≤1000000

Output

对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出Yes,否则输出No(注意大小写)。

Sample Input

aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2

Sample Output

Yes
No
Yes​

Sol

这就是一个字符串Hash的典型问题——给你一个字符串,给出两个区间,问你这两个区间的字符串是否相等。这种时候我们只需要得到这两个区间子串的Hash值,看看是否相等即可。复杂度是O(n)的。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 1000005
using namespace std;
const int p = 131, q = 2147483643;
typedef unsigned long long ll;
int read() {
    int x = 0, f = 1, ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
    return x * f;
}
 
string s;
ll sum[maxn];
int n;
ll pwr(ll a, ll b) {//求p的幂次,其实可以开一个数组来存,就不用每次都求一次快速幂了。
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % q;
        b >>= 1;
        a = a * a % q;
    }
    return ans;
}
 
signed main() {
    cin >> s;
    for(int i = 0; i < s.size(); i++)//这里的sum数组就是Hash数组。
        sum[i + 1] = (1ll * sum[i] * p + s[i] - 'a' + 1) % q;//递推式
         
    register int l1, l2, r1, r2;
    ll s1, s2;
    n = read();
    for(int i = 1; i <= n; i++) {
        l1 = read(), r1 = read(), l2 = read(), r2 = read();
        s1 = 1ll * (sum[r1] - sum[l1 - 1] * pwr(p, r1 - l1 + 1) % q + q) % q;
        s2 = 1ll * (sum[r2] - sum[l2 - 1] * pwr(p, r2 - l2 + 1) % q + q) % q;
        //注意这里对于mod q的操作,因为有mod操作后可能会出现负数,所以要+q再mod q。
        if(s1 == s2) puts("Yes");
        else puts("No");
    }

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值