Hash
Hash思想及原理
\(\quad Hash\)的思想与离散化有些许类似,都是把一个较大的域映射到一个较小的、方便比较的域中,以达到降低时间复杂度的目的。
\(\quad Hash\)的精髓在于\(Hash\)函数。它并不是一个确定的函数,而是要求各位\(Oier\)自己定义,(怎么定义?想怎么定义就怎么定义)。当处理数据时,你就可以通过\(Hash\)函数将得到的数据转化为一个数据大小合适的值,并将其映射在一个相对较小的表里,在查询时,就可以直接通过\(Hash\)值来定位数据。
\(\quad\) 这里举个简单的例子:
在处理" \(yhl\) "这个字符串时,我们可以将这三个字符的\(ASCII\)码加起来,得到的 "\(333\)"即可代表"\(yhl\)" 。 “字符串各位的\(ASCII\)码之和” 就是这个例子中的\(Hash\)函数
Hash冲突
\(\quad\)简单理解就是两个不同的数据,在经过某个\(Hash\)函数处理后,映射到了一个位置(也就是\(Hash\)函数值相等)。此时,这两个不同的数据将无法区分。
\(\quad\)可以拿上面的\(Hash\)函数举例,将字符串的各位的\(ASCII\)码相加这个\(Hash\)函数,显然,它无法处理字符出现顺序不同的字符串。
\(\quad\)也就是说,只要两个字符串含有的对应字符及数量均相同,就会被认定为是同一个字符串。
\(\quad\)所以,我们可以想一个更好用的\(Hash\)函数,来防止这种问题。
常用的\(Hash\)函数
\(\quad\)对于一个字符串,我们可以将他看作是一个\(base\)进制的数字,这样就可以保证两个字符串的\(Hash\)值不相等了。但是这又会引出另一个问题,对于一个较长的字符串,它的\(Hash\)值可能会很大,超出了我们可以接受的范围,所以我们就可以指定一个模数p,让每个\(Hash\)值都模上这个模数,这样所有字符串的\(Hash\)值就不会大于你指定的模数了。
\(\quad\)这种处理方法,缩小了\(Hash\)函数的值域,但是就会让\(Hash\)冲突的问题死灰复燃。只要两个字符串的\(Hash\)函数值在模p(先前指定的模数)意义下同余,就会被认为是一个字符串。所以我们在处理时要让p在合理的范围内尽量大一些,减小\(Hash\)冲突的概率。然而我们有着更好用(当然也更慢)的方法——双模数或多模数和双\(base\)或多\(base\)\(Hash\),还能更加保险:建一个大质数表,每次随机抽出两个质数进行\(Hash\)函数的求值,这样被卡掉的概率就可以降到最低(也许)。
\(\quad\)当然你也可以用一些更加四人的\(Hash\)函数,比如给每一位一个特定的质数,再给每个字符一个特定的质数,在算\(Hash\)函数时,求每一位字符对应质数与位数对应质数之积的积。这样还要筛质数,要么就自己背下来几个质数(doge),所以建议不要使用。
你已经通过过了新手教程,接下来开始冒险吧。
\(\qquad \qquad \qquad\) 按任意键以开始游戏(doge)
\(\quad\)就是找出一个字符串中某个字符串出现的次数,把两个字符串的\(Hash\)值都算出来,用h数组记录到第i位时较长字符串的\(Hash\)值,直接暴力枚举起点比较即可。
\(\quad\)对于某个区间内\(Hash\)值的求解,可以类比十进制数1.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int p=19260817,b=233,N=1e6+10;
int n,m;
long long h[N],op[N];
string s,sl;
void Hash(string s){
long long ans=0;
for(int i=0;i<s.length();i++){
ans=(ans*b+s[i])%p;
h[i]=ans;
}
}
int get_hash(int l,int r){
return (h[r]-1ll*(l>=1?h[l-1]:0)*op[r-l+1]%p+p)%p;
//这里用三目运算符纯粹是因为我从下标0开始计数,正常来说写h[l-1]即可。
}
long long get_Hash(string s){
long long ans=0;
for(int i=0;i<(int)s.length();i++)ans=((long long)ans*b+s[i])%p;
return ans;
}
string r(){//安利一手我的字符串快读,具体数值要根据题目调整。
char ch=getchar();string ans;
while(!(ch>=97&&ch<=122)&&!(ch>=65&&ch<=90))ch=getchar();
while((ch>=97&&ch<=122)||(ch>=65&&ch<=90))ans+=ch,ch=getchar();
return ans;
}
int main(){
scanf("%d",&m);
op[0]=1;
for(int i=1;i<N;i++)op[i]=op[i-1]*b%p;
while(m--){
s=r(),sl=r();
int l=s.length(),len=sl.length();
int o=get_Hash(s),ans=0;
Hash(sl);
for(int i=0;i<=len-l;i++){
if(get_hash(i,i+l-1)==o)ans++;
}
printf("%d\n",ans);
}
}