哈希讲解-And-洛谷-P3370 模板字符串哈希-题解
Posted Tisfy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈希讲解-And-洛谷-P3370 模板字符串哈希-题解相关的知识,希望对你有一定的参考价值。
哈希思想
说一下我对哈希算法的理解
当你要比较一些数据量很大的东西时,普通的比较可能会消耗大量的时间。
比如要看一个超高像素的图片是否在一个图集中,而这个图片有 10 亿 10亿 10亿像素,图集中有 1 万 1万 1万张图片。那么普通方法比较一张图片就需要比较 10 亿 10亿 10亿个像素点, 1 0 9 10^9 109级;和这 1 万 1万 1万张图片分别比较,又需要比较 1 0 4 10^4 104次,总计算量是 1 0 13 10^{13} 1013级别,普通计算机按一秒 1 亿 1亿 1亿次的算力来计算,需要 1 0 5 秒 ≈ 27.8 小 时 10^5秒\\approx27.8小时 105秒≈27.8小时才能得到结果。
(纯瞎说)
然而如果把图片映射成哈希值,比较两张图片的复杂度就变成了 O ( 1 ) O(1) O(1),只需要 1 0 5 10^5 105级别即可得到答案(使用特殊方法还能优化),是毫秒级别。
总之,哈希思想就是把数据量很大的一个数据,映射成一个数值,而数值之间的比较是 O ( 1 ) O(1) O(1)级别的,就能快速比较出结果来。
那么如何把一些数据给映射成一个数呢?我们先从一维数组说起:
一维数组出现的地方很多,可能是一些数,也可能是一个字符串。
先简单一点,如果确定了数组的最大长度是17,数组中每个数的范围是0 ~ 9,比如:
数组名 | 元素 |
---|---|
A | 1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,4 |
B | 5,9,8,4,7,0,2,5,6,9,4,5,6,9,8,4,5 |
C | 1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,4 |
如果我们要用普通的方法比较,大概需要:
bool ifSame(int A[], int la, int B[], int lb)
{
if(la != lb)return false;
for(int i = 0; i < la; i ++)
{
if(A[i] != B[i])return false;
}
return true;
}
这样每比较两个数组,就需要比较大约
17
17
17次。
但是如果我们把数组转换成一个unsigned long long的数:
数组名 | 元素 | 哈希值 |
---|---|---|
A | 1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,4 | 15698405684569874 |
B | 5,9,8,4,7,0,2,5,6,9,4,5,6,9,8,4,5 | 59847025694569845 |
C | 1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,4 | 15698405684569874 |
就可以写成:
typedef unsigned long long ull;
bool ifSame(ull hashA, ull hashB)
{
return hashA == hashB;
}
比较两个数组,
O
(
1
)
O(1)
O(1)的复杂度就实现了。
其中由数组映射成哈希值的函数是:
ull Hash(int A[], int la)
{
int p=10;//其实是10进制
ull ans=0;
for(int i=0;i<la;i++)
{
ans=ans*p+A[i];
}
return ans;
}
上面我们是把每个数以十进制的形式,把一个数组映射成了一个数。
其实,真正使用哈希算法的时候,我们一般不以10为进制,而经常以131为进制。为什么呢?我记得是《算法竞赛指南》上给的:以131、1331为进制的话,冲突的概率最低。
(好玄学)大概是经过多次尝试,统计出来的。
这就涉及到为什么会产生冲突,以及如何以131为进制。
如何以131为进制:
其实很简单,把10进制时公式的10换成131就行了。
ans=ans*p+A[i];//其中p=131。
如果超出unsigned long long的范围了怎么办?大概有两种方法:
- 一种是不用管,因为unsigned long long溢出相当于自动对 2 64 2^{64} 264取模。
- 另一种是手动取模,尽量找一个素数来模,每次手动%mod。(其中mod是你定义的模值)
为何会产生冲突:
其实也不难理解。你想啊,把很多数据映射到一个数,如果还不会冲突的话,那不就是无损超级压缩了么。很多数据映射到一个数,有可能不用的数据映射到的数是相同的,这就产生了冲突。
要避免冲突还有一种办法:
- 就是不只计算一个哈希值,而是选取两个数作为进制,分别计算两种进制下的哈希值。比较两堆数据时,只有两个哈希都相同,我们才认为这两堆数据是相同的。这样冲突的概率又会小很多。
暂且认为讲得差不多了,不如根据一道板子题来实战一下:
洛谷-P3370 【模板】字符串哈希
Time Limit: 500ms
Memory Limit: 125M
传送门
Description
如题,给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。
#友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)
Input
第一行包含一个整数 N N N,为字符串的个数。
接下来 N N N 行每行包含一个字符串,为所提供的字符串。
Output
输出包含一行,包含一个整数,为不同的字符串个数。
Sample Input
5
abc
aaaa
abc
abcc
12345
Sample Output
4
题目大意
给你一些字符串,问给你的字符串共有几种(去重)。
这题不适用哈希也能过,但是不要为了过题而做题,请自觉尝试使用哈希的方法通过这道题。
解题思路
我们可以把每个字符串映射成一个unsigned long long的数,之后字符串比较起来就是 O ( 1 ) O(1) O(1)的复杂度了。
AC代码
#include <bits/stdc++.h>
using namespace std;
#define mem(a) memset(a, 0, sizeof(a))
#define dbg(x) cout << #x << " = " << x << endl
#define fi(i, l, r) for (int i = l; i < r; i++)
#define cd(a) scanf("%d", &a)
typedef long long ll;
typedef unsigned long long ull;
const ull mod=100001651;//这是一个素数,本代码采用手动取模的方法,非自然溢出取模
int base=131;//131进制
ull a[10010];//a[i]初始存字符串i的哈希
ull Hash(string &s)//返回字符串s的哈希值
{
ull ans=0;//初始值是0
int l=s.size();
for(int i=0;i<l;i++)
{
ans*=base;//×进制
ans+=s[i];//+此位
ans%=mod;//手动取模
}
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
string s;
cin>>s;
a[i]=Hash(s);//记录s的哈希值
}
sort(a,a+n);//排序
int ans=1;
for(int i=1;i<n;i++)
if(a[i]!=a[i-1])//和上一个字符串的哈希不同
ans++;
cout<<ans<<endl;
return 0;
}
勇士,要挑战一下二维哈希吗
原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/116560539
以上是关于哈希讲解-And-洛谷-P3370 模板字符串哈希-题解的主要内容,如果未能解决你的问题,请参考以下文章