字符串Hash可以理解为,把一个字符串转换为一个整数。
如果我们通过某种方法,将字符串转换为一个整数,就可以便的确定某个字符串是否重复出现过。
Hash方法
给定一个字符串S=s1s2s3…sn S = s_1s_2s_3…s_nS=s
对字母x,我们规定idx(x)=x−′a′+1。
公式:
hash[i]=hash[i−1]∗p+id(s[i])。
单Hash方法:
公式: hash[i]=(hash[i−1])∗p+idx(s[i]) % mod
其中p pp和mod modmod均为质数,且有p<mod p < modp<mod。
对于此种Hash方法,将p和mod尽量取大即可,这种情况下,冲突的概率是很低的。
如取p=13,mod=101 p = 13, mod = 101p=13,mod=101,对字符串abc abcabc进行Hash
hash[0] = 1
hash[1] = (hash[0] * 13 + 2) % 101 = 15
hash[2] = (hash[1] * 13 + 3) % 101 = 97
双Hash:将一个字符串用不同的mod modmod hash两次,将这两个结果用一个二元组表示,作为Hash结果。
公式:hash1[i]=(hash1[i−1])∗p+idx(s[i]) % mod1
hash2[i]=(hash2[i−1])∗p+idx(s[i]) % mod2 hash2[i] = (hash2[i-1]) * p + idx(s[i]) \ % \ mod2hash2[i]=(hash2[i−1])∗p+idx(s[i]) % mod2
hash结果为<hash1[n],hash2[n]> <hash1[n],hash2[n]><hash1[n],hash2[n]>
这种Hash很安全的。
获取子串的Hash
如果我们求出一个串的Hash,就可以求解其子串的Hash值。
我们先以一个具体的例子来理解。
很久很久以前,森林里住着一群兔子。
有一天,兔子们想要研究自己的 DNA 序列。
我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。
然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。
输入格式
第一行输入一个 DNA 字符串 S。
第二行一个数字 m,表示 m 次询问。
接下来 m 行,每行四个数字 l1,r1,l2,r2l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。
输出格式
对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。
数据范围
1≤length(S),m≤10000001≤length(S),m≤1000000
输入样例:
aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
在这个例题中我们就需要去求一个字符串中子串的Hash的值。
我们可以运用公式:若已知一个∣S∣=n |S| = n∣S∣=n的字符串的hash值,hash[i],1≤i≤n hash[i], 1\le i \le nhash[i],1≤i≤n,其1≤l≤r≤n对应的hash值为:
hash=hash[r]−hash[l−1]∗p r−l+1;
对其子给的范围进行求hash的值,再进行比较即可。
实现代码如下:
#include<stdio.h>
#include<string.h>
typedef unsigned long long ull;
#define Hash 131
int main()
{
const int N=1e6+10;
ull p[N],sum[N];
int n,m,len,a,b,c,d,i,j;
char s[N];
scanf("%s",s);
len=strlen(s);
scanf("%d",&n);
p[0]=1;
for(i=1;i<=len;i++)
{
sum[i]=sum[i-1]*Hash+(s[i-1]-‘a’+1);
p[i]=p[i-1]*Hash;
}
for(j=1;j<=n;j++)
{
scanf("%d %d %d %d",&a,&b,&c,&d);
if(sum[b]-sum[a-1]==sum[d]-sum[c-1]*p[d-c+1])
{
printf(“Yes\n”);
}
else
printf(“NO\n”);
}
return 0;
}