首先介绍一下hash?
事实上是一种叫做蛤丝的病毒
hash的做法:
首先设一个进制数base,并设一个模数mod
而哈希其实就是把一个数转化为一个值,这个值是base进制的,储存在哈希表中,注意一下在存入的时候取模一下即可
比如说现在有一个字符串orzc
枚举这个字符串的每一位,与base相乘得到ans,然后mod一下,就得到orzc的哈希值
但是哈希有一个很大的弊端:
哈希冲突
什么是哈希冲突呢?
就比如说orzc的哈希值是233,而orzhjw的哈希值也是233
那么我们在查询的时候代码会认为这两个字符串是相同的,但显然这两个字符串是不同的
减少哈希冲突的方法很多
自然溢出法,双哈希之类的
看一道例题理解一下
洛谷P3370 【模板】字符串哈希
题目描述
如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。
友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)
输入输出格式
输入格式:
第一行包含一个整数N,为字符串的个数。
接下来N行每行包含一个字符串,为所提供的字符串。
输出格式:
输出包含一行,包含一个整数,为不同的字符串个数。
输入输出样例
输入样例#1: 复制
5 abc aaaa abc abcc 12345
输出样例#1: 复制
4
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,Mi≈6,Mmax<=15;
对于70%的数据:N<=1000,Mi≈100,Mmax<=150
对于100%的数据:N<=10000,Mi≈1000,Mmax<=1500
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。
Tip: 感兴趣的话,你们可以先看一看以下三题:
BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097
BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098
BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099
如果你仔细研究过了(或者至少仔细看过AC人数的话),我想你一定会明白字符串哈希的正确姿势的^_^
事实上如果理解了刚刚讲的hash的原理的话,这道题就很水了,因为本来就是模板题
用一段hash的代码再来巩固一下刚才的知识
#define base 233 #define inf 1<<30 ull mod=inf; //定义一个大数(最好是质数)作为模数,这里用的是1<<30 //定义一个base进制,这里是233 il ull hash(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod; } return ans; //枚举该字符串的每一位,与base相乘,转化为base进制,加(ull)是为了防止爆栈搞出一个负数,(ull)是无符号的,但其实加了一个ull是可以不用mod的,加个mod更保险 //然而加了mod会很玄学,莫名比不加mod慢了300多ms }
因为懒就没有去找一个大质数来当mod,用了1<<30代替,但是最好还是找一个大质数当mod(搜索一下生日悖论?大概就会明白原因了)
最后贴一下刚刚的例题的两种解法:
解法1:单hash/自然溢出法
这里就当一种解法来说吧
因为代码差异不大
这道题的话单hash mod开大质数是可以过的,但是在大多数难一些的题目里面是会被卡掉的#include <cstdio>
#include <cstring> #include <algorithm> #define ll int #define inf 1<<30 #define mt(x,y) memset(x,y,sizeof(x)) #define il inline #define ull unsigned long long il ll max(ll x,ll y){return x>y?x:y;} il ll min(ll x,ll y){return x<y?x:y;} il ll abs(ll x){return x>0?x:-x;} il ll swap(ll x,ll y){ll t=x;x=y;y=t;} il void read(ll &x){ x=0;ll f=1;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-f;c=getchar();} while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();} x*=f; } using namespace std; #define N 10001 #define base 233 ull mod=212370440130137957ll; ll f[N],n; char a[N];//ull hash(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=((base*ans+(ull)s[i])+mod)%mod; } return ans; }//这个是单hash+大质数mod,也是可以过的,但是会比较慢 ull hash(char s[]){//自然溢出 ull ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=base*ans+(ull)s[i]; //这里不使用mod让它自然溢出,定义为ull的数在超过2^32的时候会自然溢出 //如果把这个换成上面的hash就会400ms+ //所以说自然溢出大法好 } return ans; } int main(){ read(n); for(ll i=1;i<=n;i++){ scanf("%s",a); f[i]=hash(a); } sort(f+1,f+n+1);ll ans=1; for(ll i=1;i<n;i++){ if(f[i]!=f[i+1])ans++; } printf("%d\n",ans); return 0; }
解法2:双hash
其实就是用两个不同的mod来算hash,哈希冲突的概率是降低了很多,不过常数大,容易被卡,这道题要700ms+
本人还是更推荐自然溢出法
#include <cstdio> #include <cstring> #include <algorithm> #define ll int #define inf 1<<30 #define mt(x,y) memset(x,y,sizeof(x)) #define il inline #define ull unsigned long long il ll max(ll x,ll y){return x>y?x:y;} il ll min(ll x,ll y){return x<y?x:y;} il ll abs(ll x){return x>0?x:-x;} il ll swap(ll x,ll y){ll t=x;x=y;y=t;} il void read(ll &x){ x=0;ll f=1;char c=getchar(); while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-f;c=getchar();} while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();} x*=f; } using namespace std; #define N 10001 #define base 233 ull mod1=212370440130137957ll; ull mod2=inf; ll n; char a[N]; struct node{ll x,y;}f[N]; il ull hash1(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod1; } return ans; } il ull hash2(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=(base*ans+(ull)s[i])%mod2; } return ans; } il bool cmp1(node a,node b){return a.x<b.x;} il bool cmp2(node a,node b){return a.y<b.y;} int main(){ read(n); for(ll i=1;i<=n;i++){ scanf("%s",a); f[i].x=hash1(a); f[i].y=hash2(a); } sort(f+1,f+n+1,cmp1);sort(f+1,f+n+1,cmp2); ll ans=1; for(ll i=1;i<n;i++){ if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y)ans++; } printf("%d\n",ans); return 0; }
这道题也是可以打字典树的,rank1的dalao就是用的字典树,不过就不介绍了,其他的题解里面也有,况且既然是哈希模板,就好好打哈希咯233
我是不会承认因为我懒而且不是很懂字典树才不放字典树做法的
原文地址:https://www.cnblogs.com/henry-1202/p/8868414.html