提示:本篇文章仅仅针对普及组的OIer,并且,这是网上少有的一篇关于介绍哈希代码的文章
注:提高组的大佬们勿喷。
如题,给定 N N N个字符串(第i个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N个字符串中共有多少个不同的字符串。
第一行包含一个整数 N N N,为字符串的个数。
接下来 N N N行每行包含一个字符串,为所提供的字符串。
输出包含一行,包含一个整数,为不同的字符串个数。
5
abc
aaaa
abc
abcc
12345
4
时空限制: 1000 1000 1000ms, 128 M 128M 128M
数据规模:
对于30%的数据: N < = 10 N<=10 N<=10, M i ≈ 6 M_i≈6 Mi≈6, M m a x < = 15 M_{max}<=15 Mmax<=15;
对于60%的数据: N < = 10000 N<=10000 N<=10000, M i ≈ 100 Mi≈100 Mi≈100, M m a x < = 150 M_{max}<=150 Mmax<=150
对于100%的数据:N<=100000,Mi≈1000,Mmax<=1500
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计 4 4 4个不同的字符串。
哈希,又称散列、杂凑,英文名为Hash。
哈希函数这个东西,一般是提高组的内容,对于普及组来说,哈希的内容如下:
for(j=1;j<=s.size();j++)
pos=(pos*100+s[j])%MOD;
这里 p o s pos pos就是哈希值,而 s s s 就是一个 s t r i n g string string 类型的字符串。因为ASCII码的范围为32~128,所以这里乘上100就可以了。
当然,很多的地方都用到了Hash,比如说一款程序的密码,密码其实就是利用一种叫做 MD5码 的Hash函数来进行转换,并且产生哈希冲突的可能性为0,且无法破译(最多穷举),所以,我们 只能“重置密码”,而不能“找回密码”,就是这个原因。
当我们取哈希函数为这样的时候:
pos=a%5;
这里 p o s pos pos就是哈希值,而 s s s 就是一个 i n t int int 类型的数字。那么当 a = 2 a=2 a=2和 a = 7 a=7 a=7的时候, p o s pos pos的值都等于 2 2 2,那么该怎么办呢?其实这个就是哈希冲突。哈希的复杂度其实就在于哈希冲突的可能性,这就是要 m o d mod mod 一个大质数的原因。
设这一堆要哈希的数为
5 , 4 , 9 , 2 5,4,9,2 5,4,9,2
哈希函数:模 5 5 5
方法思路:这一个放不下了,就放下一个呗。
状态 | a 0 a_0 a0 | a 1 a_1 a1 | a 2 a_2 a2 | a 3 a_3 a3 | a 4 a_4 a4 |
---|---|---|---|---|---|
空 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 |
5 5 5进来 | 5 5 5 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 |
4 4 4进来 | 5 5 5 | − 1 -1 −1 | − 1 -1 −1 | − 1 -1 −1 | 4 4 4 |
9 9 9进来 | 5 5 5 | 9 9 9 | − 1 -1 −1 | − 1 -1 −1 | 4 4 4 |
2 2 2进来 | 5 5 5 | 9 9 9 | 2 2 2 | − 1 -1 −1 | 4 4 4 |
我们发现,当当前的数字的位置已经有数字的时候,就会到下一个位置,直到有空位为止。这种方法空间小,速度慢
代码(字符串):
int HASH (int pos){
//开放寻址法 s是输入的字符串
int cur=pos;
while(!a[cur].empty()){
if(a[cur]==s) return 0;//已经找到
cur=(cur+1)%MOD;
}
a[cur]=s;
return 1;//表示没有找到
}
方法思路:新建一个链表,将每一个Hash之后相同的字符串放在同一个链表里面即可,使用一个数组来存放链表(节省空间,防MLE).
首先开一个 h e a d head head数组。 h e a d i head_i headi表示第 i i i个下标的链表头, − 1 -1 −1表示没有,再开一个 n e x nex nex数组, n e x i nex_i nexi表示第 i i i个数的下一个的地址, − 1 -1 −1表示没有。添加的时候从头加。 a a a数组表示的是数据。 k k k表示现在储存到 a a a数组的哪一个下标了。
这种方法时间小空间大。
int HASH(int pos){
//挂链法
int cur=head[pos];
while(cur!=-1){
if(a[cur]==s) return 0;
cur=nex[cur];
}
//空出0号位置,原因:未知
a[++k]=s;
nex[k]=head[pos];
head[pos]=k;
return 1;
}
其实,这题就是一道裸的哈希,并且也可以用map来做,但是,map其实就是哈希,只不过机带的要稍微慢一点。。。
哈希函数就用处理字符串的方法::进制法,然后不管是开放寻址法还是挂链法都无所谓了,但开放寻址法还是要稍微快一点。
AC代码(这就是个模板,希望大家能加上自己的码风,然后背下来)::
#include
#include
#include
#define maxn 100039
#define MOD 1000039
using namespace std;
int n,ans,i,j,pos,k;
int head[MOD+39],nex[MOD+39];
string s,a[MOD+39];
int HASH(int pos){
//挂链法
int cur=head[pos];
while(cur!=-1){
if(a[cur]==s) return 0;
cur=nex[cur];
}
//空出0号位置,原因:未知
a[++k]=s;
nex[k]=head[pos];
head[pos]=k;
return 1;
}
/*int HASH (int pos){//开放寻址法
int cur=pos;
while(!a[cur].empty()){
if(a[cur]==s) return 0;
cur=(cur+1)%MOD;
}
a[cur]=s;
return 1;
}
*/
int main(){
scanf("%d",&n);
memset(head,-1,sizeof(head));
for(i=1;i<=n;i++){
cin>>s;
pos=0;
for(j=1;j<=s.size();j++)
pos=(pos*100+s[j])%MOD;
if(HASH(pos)) ans++;
}
printf("%d",ans);
return 0;
}
END.