字符串哈希–洛谷P3370
本文借鉴洛谷题解
题目描述
思路概述
Hash就是一个像函数一样的东西,你放进去一个值,它给你输出来一个值。输出的值就是Hash值。一般Hash值会比原来的值更好储存(更小)或比较。
那字符串Hash就非常好理解了。就是把字符串转换成一个整数的函数。而且要尽量做到使字符串对应唯一的Hash值。
首先不要把任意字符对应到数字0,比如假如把a对应到数字0,那么将不能只从Hash结果上区分ab和b(虽然可以额外判断字符串长度,但不把任意字符对应到数字0更加省事且没有任何副作用),一般而言,把a-z对应到数字1-26比较合适。
进制的选择实际上非常自由,大于所有字符对应的数字的最大值,不要含有模数的质因子,比如一个字符集是a到z的题目,选择27、233、19260817 都是可以的。到这里,字符串→数字的转换完成。
取两个差距不大的数作为模,尽量选素数,这样如果两个字符串转换形成的数对两个模分别取余,可以认为原本这两个字符串完全一样。到这里,判断完成。
题解
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
struct data
{
ull x;//存储hash1得到的结果
ull y;//存储hash1得到的结果
}a[10010];
/*对结构体成员排序*/
bool cmp(data a,data b)
{
return a.x<b.x;
}
char s[10010];//s存储一行字符串
int n,ans=1;
//选取两个相近的较大数字
ull mod1=19260817;
ull mod2=19660813;
/*对第一个大数取模*/
ull hash1(char s[])
{
int len=strlen(s);
ull ans=0;
for(int i=0;i<len;i++)
{
/*将字符转化为数字,不要选取0,因为这样a,ab无法直接判断
所以a~z视为1~26,以26为进制,所以每次将上一位数乘27*/
ans=(ans*27+(ull)s[i])%mod1;
}
//返回余数
return ans;
}
/*对第二个大数取模*/
ull hash2(char s[])
{
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*27+(ull)s[i])%mod2;
return ans;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s;
a[i].x=hash1(s);
a[i].y=hash2(s);
}
sort(a+1,a+n+1,cmp);//从小到大排序
for(int i=2;i<=n;i++)//下面有减1,所以从2开始
{
//只有两个数对于mod1,mod2的余数都相等,才算重复
//已经排好序,只用比较相邻元素,不需要循环
if(a[i].x!=a[i-1].x||a[i-1].y!=a[i].y)
ans++;
}
cout<<ans<<endl;
return 0;
}