如果你已经确保自己的hash技巧已经入门,那么请左转这篇博文

首先介绍一下hash?

事实上是一种叫做蛤丝的病毒

以下讲到的hash都是OI中最常用到的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=,len=strlen(s);
for(ll i=;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>?x:-x;}
il ll swap(ll x,ll y){ll t=x;x=y;y=t;}
il void read(ll &x){
x=;ll f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-f;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';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=,len=strlen(s);
for(ll i=;i<len;i++){
ans=base*ans+(ull)s[i];
//这里不使用mod让它自然溢出,定义为ull的数在超过2^32的时候会自然溢出
//如果把这个换成上面的hash就会400ms+
//所以说自然溢出大法好
}
return ans;
}
int main(){
read(n);
for(ll i=;i<=n;i++){
scanf("%s",a);
f[i]=hash(a);
}
sort(f+,f+n+);ll ans=;
for(ll i=;i<n;i++){
if(f[i]!=f[i+])ans++;
}
printf("%d\n",ans);
return ;
}

解法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>?x:-x;}
il ll swap(ll x,ll y){ll t=x;x=y;y=t;}
il void read(ll &x){
x=;ll f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-f;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';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=,len=strlen(s);
for(ll i=;i<len;i++){
ans=(base*ans+(ull)s[i])%mod1;
}
return ans;
}
il ull hash2(char s[]){
ll ans=,len=strlen(s);
for(ll i=;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=;i<=n;i++){
scanf("%s",a);
f[i].x=hash1(a);
f[i].y=hash2(a);
}
sort(f+,f+n+,cmp1);sort(f+,f+n+,cmp2);
ll ans=;
for(ll i=;i<n;i++){
if(f[i].x!=f[i+].x||f[i].y!=f[i+].y)ans++;
}
printf("%d\n",ans);
return ;
}

这道题也是可以打字典树的,也是裸的做法,读者也可以尝试一下,因为这里是讲hash的所以就不放字典树的代码了

hash详解的更多相关文章

  1. 探索c#之一致性Hash详解

    阅读目录: 使用场景 算法原理 虚拟节点 代码示例 使用场景 以Redis为例,当系统需要缓存的内容超过单机内存大小时,例如要缓存100G数据,单机内存仅有16G时.这时候就需要考虑进行缓存数据分片, ...

  2. location.hash详解

    一.#的涵义 #代表网页中的一个位置.其右面的字符,就是该位置的标识符.比如, http://www.example.com/index.html#print 就代表网页index.html的prin ...

  3. location.hash 详解

    前年9月twitter改版. 一个显著变化,就是URL加入了"#!"符号.比如,改版前的用户主页网址为 http://twitter.com/username 改版后,就变成了 h ...

  4. redis详解(四)-- 高可用分布式集群

    一,高可用 高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响. 停止服务的原因可能由于网卡.路由器.机房.CPU负载过高.内存溢出.自然灾害等不可预期的原 ...

  5. redis 五种数据结构详解(string,list,set,zset,hash)

    redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...

  6. $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    $.ajax()方法详解   jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...

  7. redis 五种数据结构详解(string,list,set,zset,hash),各种问题综合

    redis 五种数据结构详解(string,list,set,zset,hash) https://www.cnblogs.com/sdgf/p/6244937.html redis 与 spring ...

  8. 多表连接的三种方式详解 hash join、merge join、 nested loop

    在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式.多表之间的连接有三种方式:Nested Loops,Hash Join 和 Sort Merge Join.具体适用哪 ...

  9. 【Redis】redis 五种数据结构详解(string,list,set,zset,hash)

    redis 五种数据结构详解(string,list,set,zset,hash) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存 ...

随机推荐

  1. Spring Cloud Contract

    http://www.infoq.com/cn/news/2017/04/spring-cloud-contract 在默认情况下,我们希望用户以JAR文件的形式将生产者存根和契约发布到Maven库. ...

  2. 对Java的初步的认识:

    学习JAVA的优势 1) Java广受欢迎 Java是世界上最受欢迎的编程语言之一,有无限多种方法使用Java.据2016年数据显示,Java被评为最受欢迎的编程语言之一2)  Java薪资比较好最新 ...

  3. C#/AutoCAD 2018/ObjectArx/二次开发添加图形对象步骤和添加直线的例子(三)

    1.创建一个图形对象的步骤如下(1)得到创建对象的图形数据库:(2)在内存中创建实体类的一个对象:(3)定义一个指向当前数据库的事务处理:(4)打开图形数据库的块表:(5)打开一个存储实体的块表记录( ...

  4. 冲刺NO.8

    Alpha冲刺第八天 站立式会议 项目进展 项目稳步进行,项目的基础部分如基本信息管理,信用信息管理等部分已相对比较完善. 问题困难 技术困难在短期内很难发生质的变化,而本项目由于选择了队员不太熟悉的 ...

  5. Linux下进程间通信--消息队列

    消息队列的定义遍地都是,不想移驾,请看下文: 一.定义: 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法. 每个数据块都被认 为是有一个类型,接收者进程接收的数据块可以有不同的类型值.我 ...

  6. Java基础类库简介

    Java基础类库简介 一.常用的基础类库:11个jar(Java Archive,Java归档)包 作为java语言使用者,我们可以感受到java语言带来的优势(平台无关.面向对象.多线程.高效易扩展 ...

  7. MSIL实用指南-生成接口

    本篇讲解怎么样生成接口,即interface. 一.创建类型创建一个接口类型依旧用ModuleBuilder的DefineType方法,但是它的第二个参数必须要有TypeAttributes.Inte ...

  8. leetcode题解 6.ZigZag Conversion

    6.ZigZag Conversion 题目: The string "PAYPALISHIRING" is written in a zigzag pattern on a gi ...

  9. 常用的汇编指令 movs stos

    movsb   把寄存机esi所存的地址的数据以字节复制到edi movsw  把寄存机esi所存的地址的数据以word复制到edi movsd   把寄存机esi所存的地址的数据以dword复制到e ...

  10. emqtt 试用(三)mqtt 知识

    一.概念 MQTT 协议客户端库: https://github.com/mqtt/mqtt.github.io/wiki/libraries 例如,mosquitto_sub/pub 命令行发布订阅 ...