哈希讲解-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小时 10527.8才能得到结果。
(纯瞎说)
然而如果把图片映射成哈希值,比较两张图片的复杂度就变成了 O ( 1 ) O(1) O(1),只需要 1 0 5 10^5 105级别即可得到答案(使用特殊方法还能优化),是毫秒级别。

总之,哈希思想就是把数据量很大的一个数据,映射成一个数值,而数值之间的比较是 O ( 1 ) O(1) O(1)级别的,就能快速比较出结果来。

那么如何把一些数据给映射成一个数呢?我们先从一维数组说起:
一维数组出现的地方很多,可能是一些数,也可能是一个字符串。
先简单一点,如果确定了数组的最大长度是17,数组中每个数的范围是0 ~ 9,比如:

数组名元素
A1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,4
B5,9,8,4,7,0,2,5,6,9,4,5,6,9,8,4,5
C1,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的数:

数组名元素哈希值
A1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,415698405684569874
B5,9,8,4,7,0,2,5,6,9,4,5,6,9,8,4,559847025694569845
C1,5,6,9,8,4,0,5,6,8,4,5,6,9,8,7,415698405684569874

就可以写成:

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 模板字符串哈希-题解的主要内容,如果未能解决你的问题,请参考以下文章

洛谷 P3370 模板字符串哈希

[洛谷P3370]模板字符串哈希

洛谷 P3370 模板字符串哈希

洛谷——P3370 模板字符串哈希

P3370 模板字符串哈希字符串哈希

P3370 模板字符串哈希