哈希入门
哈希原名Hash,意译是散列算法,“哈希”为音译。它通过一种压缩的映射将任意长度的输入转换成固定输出,即散列值。由于哈希不是一一映射,所以不同输入可以被映射成同一个输出,但这样的概率极小——除非题目针对你或者你脸黑。Hash据说是整个密码学的基础,所以身为一个通过了字符串哈希模板的人,我深感自豪
先发两道luogu的Hash题目,因为我才刚学哈希,所以做的都是普及-的题。一道是模板,另一道叫做谷仓里的回声。
这两道题都是用哈希来对字符串进行判重,即判断两个字符串(或子串)是否相同。那么怎么判断呢?
我们知道,完全不同的字符串在每一位上的字符都不同,转换成整型就是每一位的数字都不同(废话)。所以对于两个字符串,可以采用把它的每一位相加的办法来得到一个数值,通过判断两个数值是否相等来判断两个字符串是否相同。
比如“abc”,通过上面的办法得到的数值就是‘a'+‘b’+‘c’=97+98+99=294(口算的,不一定对)。如果是“bcd”字符串,它的标志数值就是297,明显跟294不一样,所以这两个数值的主人——两个字符串当然也不一样。
但是很明显的,用这种方法来判重就像把你的生日加起来作为你的身份证号一样不靠谱。且不说上面的方法会把“abc”和“cba”当成同一个字符串,我给字符串取个中值呢?“bbb”的标志数值不也是294吗?所以明显要用另一种方式。
一种比较神奇的方式是,我给每次运算时得到的数值乘个质数。因为我们知道,任意一个二进制数字都可以用2的n次方相加来表示。比如100101,有6位且最高位是1,次高位是0。。。
所以100101可以等于1*2^5+0*2^4+0*2^3+...=2^5+2^2+2^0=32+4+1=37。这个很好想,再举一个十进制的例子,12345=1*10^4+2*10^3+3*10^2+4*10^1+5*10^0。对吧。哈希用的也是一样的原理,用一个质数来区别每一位,即ha[i]=ha[i-1]*163(个人比较喜欢用163)+字符串[i],这样就可以区分“abc”和“cba”,也可以区分“abc”和“bbb”。
但是这样太容易溢出了。溢出不就全完了吗?
所以我们用unsigned long long 来储存哈希值,当它溢出的时候实际是对你哈希值的自动取余,并且取余后的哈希值仍然有大概率唯一(之所以是大概率,因为取余也有可能会导致两个不同字符串的哈希值相同,从而撞车。你可以多换几个质数,比如107,131等等)。不知道为什么自动取余的可以看我另一篇博客数据类型溢出详解
好了入门哈希就讲到这里,发上两道题的代码
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 typedef unsigned long long ULL; 6 ULL h[100000]; 7 ULL seed=163; 8 inline ULL hash(string x){ 9 ULL Hash=0; 10 int len=x.length()-1; 11 for(int i=0;i<=len;++i){ 12 Hash=Hash*seed+x[i]-'0'; 13 } 14 return Hash; 15 } 16 17 int main(){ 18 int n; 19 cin>>n; 20 string que; 21 for(int i=1;i<=n;++i){ 22 cin>>que; 23 h[i]=hash(que); 24 } 25 sort(h+1,h+n+1); 26 int ans=0; 27 for(int i=1;i<=n;++i){ 28 if(h[i]!=h[i+1]) ans++; 29 } 30 cout<<ans; 31 return 0; 32 }
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 typedef unsigned long long ULL; 6 char a[1000],b[1000]; 7 ULL seed[1000]={1}; 8 ULL ha[1000],hb[1000]; 9 ULL ta[1000],tb[1000]; 10 int main(){ 11 a[0]=b[0]='0'; 12 cin>>a+1>>b+1; 13 int n=strlen(a)-1,m=strlen(b)-1; 14 int len=n>m?n:m; 15 int dlen=n<m?n:m; 16 for(int i=1;i<=len;++i) seed[i]=seed[i-1]*163; 17 for(int i=1;i<=n;++i) ha[i]=ha[i-1]*163+a[i]; 18 for(int i=1;i<=m;++i) hb[i]=hb[i-1]*163+b[i]; 19 for(int i=n;i>=1;--i) ta[i]=ta[i+1]+seed[n-i]*a[i]; 20 for(int i=m;i>=1;--i) tb[i]=tb[i+1]+seed[m-i]*b[i]; 21 int ans=0; 22 for(int i=1;i<=dlen;++i) 23 if(ha[i]==tb[m-i+1]||hb[i]==ta[n-i+1]) ans=i; 24 cout<<ans<<endl; 25 return 0; 26 }